1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.database.sqlite;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 
21 import java.util.ArrayList;
22 import java.util.Locale;
23 import java.util.regex.Pattern;
24 
25 /**
26  * Describes how to configure a database.
27  * <p>
28  * The purpose of this object is to keep track of all of the little
29  * configuration settings that are applied to a database after it
30  * is opened so that they can be applied to all connections in the
31  * connection pool uniformly.
32  * </p><p>
33  * Each connection maintains its own copy of this object so it can
34  * keep track of which settings have already been applied.
35  * </p>
36  *
37  * @hide
38  */
39 public final class SQLiteDatabaseConfiguration {
40     // The pattern we use to strip email addresses from database paths
41     // when constructing a label to use in log messages.
42     private static final Pattern EMAIL_IN_DB_PATTERN =
43             Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
44 
45     /**
46      * Special path used by in-memory databases.
47      */
48     public static final String MEMORY_DB_PATH = ":memory:";
49 
50     /**
51      * The database path.
52      */
53     public final String path;
54 
55     /**
56      * The label to use to describe the database when it appears in logs.
57      * This is derived from the path but is stripped to remove PII.
58      */
59     public final String label;
60 
61     /**
62      * The flags used to open the database.
63      */
64     public int openFlags;
65 
66     /**
67      * The maximum size of the prepared statement cache for each database connection.
68      * Must be non-negative.
69      *
70      * Default is 25.
71      */
72     @UnsupportedAppUsage
73     public int maxSqlCacheSize;
74 
75     /**
76      * The database locale.
77      *
78      * Default is the value returned by {@link Locale#getDefault()}.
79      */
80     public Locale locale;
81 
82     /**
83      * True if foreign key constraints are enabled.
84      *
85      * Default is false.
86      */
87     public boolean foreignKeyConstraintsEnabled;
88 
89     /**
90      * The custom functions to register.
91      */
92     public final ArrayList<SQLiteCustomFunction> customFunctions =
93             new ArrayList<SQLiteCustomFunction>();
94 
95     /**
96      * The size in bytes of each lookaside slot
97      *
98      * <p>If negative, the default lookaside configuration will be used
99      */
100     public int lookasideSlotSize = -1;
101 
102     /**
103      * The total number of lookaside memory slots per database connection
104      *
105      * <p>If negative, the default lookaside configuration will be used
106      */
107     public int lookasideSlotCount = -1;
108 
109     /**
110      * The number of milliseconds that SQLite connection is allowed to be idle before it
111      * is closed and removed from the pool.
112      * <p>By default, idle connections are not closed
113      */
114     public long idleConnectionTimeoutMs = Long.MAX_VALUE;
115 
116     /**
117      * Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
118      * <p>Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()}
119      */
120     public String journalMode;
121 
122     /**
123      * Synchronous mode to use.
124      * <p>Default is returned by {@link SQLiteGlobal#getDefaultSyncMode()}
125      * or {@link SQLiteGlobal#getWALSyncMode()} depending on journal mode
126      */
127     public String syncMode;
128 
129     /**
130      * Creates a database configuration with the required parameters for opening a
131      * database and default values for all other parameters.
132      *
133      * @param path The database path.
134      * @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}.
135      */
SQLiteDatabaseConfiguration(String path, int openFlags)136     public SQLiteDatabaseConfiguration(String path, int openFlags) {
137         if (path == null) {
138             throw new IllegalArgumentException("path must not be null.");
139         }
140 
141         this.path = path;
142         label = stripPathForLogs(path);
143         this.openFlags = openFlags;
144 
145         // Set default values for optional parameters.
146         maxSqlCacheSize = 25;
147         locale = Locale.getDefault();
148     }
149 
150     /**
151      * Creates a database configuration as a copy of another configuration.
152      *
153      * @param other The other configuration.
154      */
SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other)155     public SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) {
156         if (other == null) {
157             throw new IllegalArgumentException("other must not be null.");
158         }
159 
160         this.path = other.path;
161         this.label = other.label;
162         updateParametersFrom(other);
163     }
164 
165     /**
166      * Updates the non-immutable parameters of this configuration object
167      * from the other configuration object.
168      *
169      * @param other The object from which to copy the parameters.
170      */
updateParametersFrom(SQLiteDatabaseConfiguration other)171     public void updateParametersFrom(SQLiteDatabaseConfiguration other) {
172         if (other == null) {
173             throw new IllegalArgumentException("other must not be null.");
174         }
175         if (!path.equals(other.path)) {
176             throw new IllegalArgumentException("other configuration must refer to "
177                     + "the same database.");
178         }
179 
180         openFlags = other.openFlags;
181         maxSqlCacheSize = other.maxSqlCacheSize;
182         locale = other.locale;
183         foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled;
184         customFunctions.clear();
185         customFunctions.addAll(other.customFunctions);
186         lookasideSlotSize = other.lookasideSlotSize;
187         lookasideSlotCount = other.lookasideSlotCount;
188         idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
189         journalMode = other.journalMode;
190         syncMode = other.syncMode;
191     }
192 
193     /**
194      * Returns true if the database is in-memory.
195      * @return True if the database is in-memory.
196      */
isInMemoryDb()197     public boolean isInMemoryDb() {
198         return path.equalsIgnoreCase(MEMORY_DB_PATH);
199     }
200 
isLegacyCompatibilityWalEnabled()201     boolean isLegacyCompatibilityWalEnabled() {
202         return journalMode == null && syncMode == null
203                 && (openFlags & SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL) != 0;
204     }
205 
stripPathForLogs(String path)206     private static String stripPathForLogs(String path) {
207         if (path.indexOf('@') == -1) {
208             return path;
209         }
210         return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY");
211     }
212 
isLookasideConfigSet()213     boolean isLookasideConfigSet() {
214         return lookasideSlotCount >= 0 && lookasideSlotSize >= 0;
215     }
216 }
217