/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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; import java.util.function.UnaryOperator; import java.util.regex.Pattern; /** * Describes how to configure a database. *

* The purpose of this object is to keep track of all of the little * configuration settings that are applied to a database after it * is opened so that they can be applied to all connections in the * connection pool uniformly. *

* Each connection maintains its own copy of this object so it can * keep track of which settings have already been applied. *

* * @hide */ public final class SQLiteDatabaseConfiguration { // The pattern we use to strip email addresses from database paths // when constructing a label to use in log messages. private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+"); /** * Special path used by in-memory databases. */ public static final String MEMORY_DB_PATH = ":memory:"; /** * The database path. */ public final String path; /** * The label to use to describe the database when it appears in logs. * This is derived from the path but is stripped to remove PII. */ public final String label; /** * The flags used to open the database. */ public int openFlags; /** * The maximum size of the prepared statement cache for each database connection. * Must be non-negative. * * Default is 25. */ @UnsupportedAppUsage public int maxSqlCacheSize; /** * The database locale. * * Default is the value returned by {@link Locale#getDefault()}. */ public Locale locale; /** * True if foreign key constraints are enabled. * * Default is false. */ public boolean foreignKeyConstraintsEnabled; /** * The custom scalar functions to register. */ public final ArrayMap> customScalarFunctions = new ArrayMap<>(); /** * The custom aggregate functions to register. */ public final ArrayMap> customAggregateFunctions = new ArrayMap<>(); /** * The statements to execute to initialize each connection. */ public final ArrayList> perConnectionSql = new ArrayList<>(); /** * The size in bytes of each lookaside slot * *

If negative, the default lookaside configuration will be used */ public int lookasideSlotSize = -1; /** * The total number of lookaside memory slots per database connection * *

If negative, the default lookaside configuration will be used */ public int lookasideSlotCount = -1; /** * The number of milliseconds that SQLite connection is allowed to be idle before it * is closed and removed from the pool. *

By default, idle connections are not closed */ public long idleConnectionTimeoutMs = Long.MAX_VALUE; /** * Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set. *

Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()} */ 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 @SQLiteDatabase.SyncMode String syncMode; public boolean shouldTruncateWalFile; /** * Creates a database configuration with the required parameters for opening a * database and default values for all other parameters. * * @param path The database path. * @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}. */ public SQLiteDatabaseConfiguration(String path, int openFlags) { if (path == null) { throw new IllegalArgumentException("path must not be null."); } this.path = path; label = stripPathForLogs(path); this.openFlags = openFlags; // Set default values for optional parameters. maxSqlCacheSize = 25; locale = Locale.getDefault(); } /** * Creates a database configuration as a copy of another configuration. * * @param other The other configuration. */ public SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) { if (other == null) { throw new IllegalArgumentException("other must not be null."); } this.path = other.path; this.label = other.label; updateParametersFrom(other); } /** * Updates the non-immutable parameters of this configuration object * from the other configuration object. * * @param other The object from which to copy the parameters. */ public void updateParametersFrom(SQLiteDatabaseConfiguration other) { if (other == null) { throw new IllegalArgumentException("other must not be null."); } if (!path.equals(other.path)) { throw new IllegalArgumentException("other configuration must refer to " + "the same database."); } openFlags = other.openFlags; maxSqlCacheSize = other.maxSqlCacheSize; locale = other.locale; foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled; customScalarFunctions.clear(); customScalarFunctions.putAll(other.customScalarFunctions); customAggregateFunctions.clear(); customAggregateFunctions.putAll(other.customAggregateFunctions); perConnectionSql.clear(); perConnectionSql.addAll(other.perConnectionSql); lookasideSlotSize = other.lookasideSlotSize; lookasideSlotCount = other.lookasideSlotCount; idleConnectionTimeoutMs = other.idleConnectionTimeoutMs; journalMode = other.journalMode; syncMode = other.syncMode; } /** * Returns true if the database is in-memory. * @return True if the database is in-memory. */ public boolean isInMemoryDb() { 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; } private static String stripPathForLogs(String path) { if (path.indexOf('@') == -1) { return path; } return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY"); } 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.getWALSyncMode(); } } 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)); } }