Support for lookaside configuration params
Modified SQLiteDatabase to allow passing lookaside configuration to its
static initializer. Encapsulated config in OpenParams and added an
overloaded version of openDatabase with the new parameter.
Configuration is changed in SQLiteConnection::nativeOpen, immediately
after opening the database since lookaside memory configuration can only
be changed when no connection is using it.
Added SQLiteOpenHelper.setLookasideConfig method that is called from
the constructor of the subclass.
Test: bit FrameworksCoreTests:android.database.DatabaseGeneralTest
Test: bit FrameworksCoreTests:android.database.SQLiteOpenHelperTest
Bug: 38499845
Change-Id: Ifb761229b43c89c090939030fc25b8c480b9b9e2
diff --git a/api/current.txt b/api/current.txt
index 0eab1fd..38829b8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11868,6 +11868,7 @@
method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(java.lang.String) throws android.database.SQLException;
method public static android.database.sqlite.SQLiteDatabase create(android.database.sqlite.SQLiteDatabase.CursorFactory);
+ method public static android.database.sqlite.SQLiteDatabase createInMemory(android.database.sqlite.SQLiteDatabase.OpenParams);
method public int delete(java.lang.String, java.lang.String, java.lang.String[]);
method public static boolean deleteDatabase(java.io.File);
method public void disableWriteAheadLogging();
@@ -11897,6 +11898,7 @@
method public boolean needUpgrade(int);
method protected void onAllReferencesReleased();
method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, int);
+ method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.OpenParams);
method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, int, android.database.DatabaseErrorHandler);
method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.io.File, android.database.sqlite.SQLiteDatabase.CursorFactory);
method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory);
@@ -11947,6 +11949,26 @@
method public abstract android.database.Cursor newCursor(android.database.sqlite.SQLiteDatabase, android.database.sqlite.SQLiteCursorDriver, java.lang.String, android.database.sqlite.SQLiteQuery);
}
+ public static final class SQLiteDatabase.OpenParams {
+ method public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
+ method public android.database.DatabaseErrorHandler getErrorHandler();
+ method public int getLookasideSlotCount();
+ method public int getLookasideSlotSize();
+ method public int getOpenFlags();
+ }
+
+ public static final class SQLiteDatabase.OpenParams.Builder {
+ ctor public SQLiteDatabase.OpenParams.Builder();
+ ctor public SQLiteDatabase.OpenParams.Builder(android.database.sqlite.SQLiteDatabase.OpenParams);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder addOpenFlags(int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams build();
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(android.database.DatabaseErrorHandler);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setLookasideConfig(int, int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setOpenFlags(int);
+ }
+
public class SQLiteDatabaseCorruptException extends android.database.sqlite.SQLiteException {
ctor public SQLiteDatabaseCorruptException();
ctor public SQLiteDatabaseCorruptException(java.lang.String);
@@ -12000,6 +12022,7 @@
method public void onDowngrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void onOpen(android.database.sqlite.SQLiteDatabase);
method public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase, int, int);
+ method public void setLookasideConfig(int, int);
method public void setWriteAheadLoggingEnabled(boolean);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 833e1f6..e4d399c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -12655,6 +12655,7 @@
method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(java.lang.String) throws android.database.SQLException;
method public static android.database.sqlite.SQLiteDatabase create(android.database.sqlite.SQLiteDatabase.CursorFactory);
+ method public static android.database.sqlite.SQLiteDatabase createInMemory(android.database.sqlite.SQLiteDatabase.OpenParams);
method public int delete(java.lang.String, java.lang.String, java.lang.String[]);
method public static boolean deleteDatabase(java.io.File);
method public void disableWriteAheadLogging();
@@ -12684,6 +12685,7 @@
method public boolean needUpgrade(int);
method protected void onAllReferencesReleased();
method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, int);
+ method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.OpenParams);
method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, int, android.database.DatabaseErrorHandler);
method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.io.File, android.database.sqlite.SQLiteDatabase.CursorFactory);
method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory);
@@ -12734,6 +12736,26 @@
method public abstract android.database.Cursor newCursor(android.database.sqlite.SQLiteDatabase, android.database.sqlite.SQLiteCursorDriver, java.lang.String, android.database.sqlite.SQLiteQuery);
}
+ public static final class SQLiteDatabase.OpenParams {
+ method public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
+ method public android.database.DatabaseErrorHandler getErrorHandler();
+ method public int getLookasideSlotCount();
+ method public int getLookasideSlotSize();
+ method public int getOpenFlags();
+ }
+
+ public static final class SQLiteDatabase.OpenParams.Builder {
+ ctor public SQLiteDatabase.OpenParams.Builder();
+ ctor public SQLiteDatabase.OpenParams.Builder(android.database.sqlite.SQLiteDatabase.OpenParams);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder addOpenFlags(int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams build();
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(android.database.DatabaseErrorHandler);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setLookasideConfig(int, int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setOpenFlags(int);
+ }
+
public class SQLiteDatabaseCorruptException extends android.database.sqlite.SQLiteException {
ctor public SQLiteDatabaseCorruptException();
ctor public SQLiteDatabaseCorruptException(java.lang.String);
@@ -12787,6 +12809,7 @@
method public void onDowngrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void onOpen(android.database.sqlite.SQLiteDatabase);
method public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase, int, int);
+ method public void setLookasideConfig(int, int);
method public void setWriteAheadLoggingEnabled(boolean);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index e6c22ae..c91d56e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -11911,6 +11911,7 @@
method public void beginTransactionWithListenerNonExclusive(android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(java.lang.String) throws android.database.SQLException;
method public static android.database.sqlite.SQLiteDatabase create(android.database.sqlite.SQLiteDatabase.CursorFactory);
+ method public static android.database.sqlite.SQLiteDatabase createInMemory(android.database.sqlite.SQLiteDatabase.OpenParams);
method public int delete(java.lang.String, java.lang.String, java.lang.String[]);
method public static boolean deleteDatabase(java.io.File);
method public void disableWriteAheadLogging();
@@ -11940,6 +11941,7 @@
method public boolean needUpgrade(int);
method protected void onAllReferencesReleased();
method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, int);
+ method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.OpenParams);
method public static android.database.sqlite.SQLiteDatabase openDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory, int, android.database.DatabaseErrorHandler);
method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.io.File, android.database.sqlite.SQLiteDatabase.CursorFactory);
method public static android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, android.database.sqlite.SQLiteDatabase.CursorFactory);
@@ -11990,6 +11992,26 @@
method public abstract android.database.Cursor newCursor(android.database.sqlite.SQLiteDatabase, android.database.sqlite.SQLiteCursorDriver, java.lang.String, android.database.sqlite.SQLiteQuery);
}
+ public static final class SQLiteDatabase.OpenParams {
+ method public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
+ method public android.database.DatabaseErrorHandler getErrorHandler();
+ method public int getLookasideSlotCount();
+ method public int getLookasideSlotSize();
+ method public int getOpenFlags();
+ }
+
+ public static final class SQLiteDatabase.OpenParams.Builder {
+ ctor public SQLiteDatabase.OpenParams.Builder();
+ ctor public SQLiteDatabase.OpenParams.Builder(android.database.sqlite.SQLiteDatabase.OpenParams);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder addOpenFlags(int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams build();
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(android.database.DatabaseErrorHandler);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setLookasideConfig(int, int);
+ method public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setOpenFlags(int);
+ }
+
public class SQLiteDatabaseCorruptException extends android.database.sqlite.SQLiteException {
ctor public SQLiteDatabaseCorruptException();
ctor public SQLiteDatabaseCorruptException(java.lang.String);
@@ -12043,6 +12065,7 @@
method public void onDowngrade(android.database.sqlite.SQLiteDatabase, int, int);
method public void onOpen(android.database.sqlite.SQLiteDatabase);
method public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase, int, int);
+ method public void setLookasideConfig(int, int);
method public void setWriteAheadLoggingEnabled(boolean);
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 34a9523..f894f05 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -16,9 +16,6 @@
package android.database.sqlite;
-import dalvik.system.BlockGuard;
-import dalvik.system.CloseGuard;
-
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
@@ -32,11 +29,14 @@
import android.util.LruCache;
import android.util.Printer;
+import dalvik.system.BlockGuard;
+import dalvik.system.CloseGuard;
+
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
-import java.util.regex.Pattern;
+
/**
* Represents a SQLite database connection.
@@ -118,7 +118,8 @@
private int mCancellationSignalAttachCount;
private static native long nativeOpen(String path, int openFlags, String label,
- boolean enableTrace, boolean enableProfile);
+ boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
+ int lookasideSlotCount);
private static native void nativeClose(long connectionPtr);
private static native void nativeRegisterCustomFunction(long connectionPtr,
SQLiteCustomFunction function);
@@ -208,8 +209,8 @@
private void open() {
mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
mConfiguration.label,
- SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
-
+ SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME,
+ mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
setPageSize();
setForeignKeyModeFromConfiguration();
setWalModeFromConfiguration();
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index fe849b8..7406b3f 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentValues;
@@ -34,10 +36,14 @@
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.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -253,11 +259,14 @@
*/
public static final int MAX_SQL_CACHE_SIZE = 100;
- private SQLiteDatabase(String path, int openFlags, CursorFactory cursorFactory,
- DatabaseErrorHandler errorHandler) {
+ private SQLiteDatabase(final String path, final int openFlags,
+ CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
+ int lookasideSlotSize, int lookasideSlotCount) {
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
+ mConfigurationLocked.lookasideSlotSize = lookasideSlotSize;
+ mConfigurationLocked.lookasideSlotCount = lookasideSlotCount;
}
@Override
@@ -667,11 +676,30 @@
* @return the newly opened database
* @throws SQLiteException if the database cannot be opened
*/
- public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
+ public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
+ @DatabaseOpenFlags int flags) {
return openDatabase(path, factory, flags, null);
}
/**
+ * Open the database according to the specified {@link OpenParams parameters}
+ *
+ * @param path to database file to open and/or create
+ * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}
+ * @return the newly opened database
+ * @throws SQLiteException if the database cannot be opened
+ */
+ public static SQLiteDatabase openDatabase(@NonNull String path,
+ @NonNull OpenParams openParams) {
+ Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
+ SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
+ openParams.mCursorFactory, openParams.mErrorHandler,
+ openParams.mLookasideSlotSize, openParams.mLookasideSlotCount);
+ db.open();
+ return db;
+ }
+
+ /**
* Open the database according to the flags {@link #OPEN_READWRITE}
* {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
*
@@ -690,9 +718,9 @@
* @return the newly opened database
* @throws SQLiteException if the database cannot be opened
*/
- public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
- DatabaseErrorHandler errorHandler) {
- SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
+ public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
+ @DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
+ SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1);
db.open();
return db;
}
@@ -700,22 +728,24 @@
/**
* Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
*/
- public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) {
+ public static SQLiteDatabase openOrCreateDatabase(@NonNull File file,
+ @Nullable CursorFactory factory) {
return openOrCreateDatabase(file.getPath(), factory);
}
/**
* Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY).
*/
- public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
+ public static SQLiteDatabase openOrCreateDatabase(@NonNull String path,
+ @Nullable CursorFactory factory) {
return openDatabase(path, factory, CREATE_IF_NECESSARY, null);
}
/**
* Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler).
*/
- public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory,
- DatabaseErrorHandler errorHandler) {
+ public static SQLiteDatabase openOrCreateDatabase(@NonNull String path,
+ @Nullable CursorFactory factory, @Nullable DatabaseErrorHandler errorHandler) {
return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
}
@@ -726,7 +756,7 @@
* @param file The database file path.
* @return True if the database was successfully deleted.
*/
- public static boolean deleteDatabase(File file) {
+ public static boolean deleteDatabase(@NonNull File file) {
if (file == null) {
throw new IllegalArgumentException("file must not be null");
}
@@ -825,13 +855,29 @@
* cursor when query is called
* @return a SQLiteDatabase object, or null if the database can't be created
*/
- public static SQLiteDatabase create(CursorFactory factory) {
+ @NonNull
+ public static SQLiteDatabase create(@Nullable CursorFactory factory) {
// This is a magic string with special meaning for SQLite.
return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
factory, CREATE_IF_NECESSARY);
}
/**
+ * Create a memory backed SQLite database. Its contents will be destroyed
+ * when the database is closed.
+ *
+ * <p>Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.</p>
+ * @param openParams configuration parameters that are used for opening SQLiteDatabase
+ * @return a SQLiteDatabase object, or null if the database can't be created
+ */
+ @NonNull
+ public static SQLiteDatabase createInMemory(@NonNull OpenParams openParams) {
+ return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
+ openParams.toBuilder().addOpenFlags(CREATE_IF_NECESSARY).build());
+ }
+
+ /**
* Registers a CustomFunction callback as a function that can be called from
* SQLite database triggers.
*
@@ -2210,4 +2256,238 @@
public interface CustomFunction {
public void callback(String[] args);
}
+
+ /**
+ * Wrapper for configuration parameters that are used for opening {@link SQLiteDatabase}
+ */
+ public static final class OpenParams {
+ private final int mOpenFlags;
+ private final CursorFactory mCursorFactory;
+ private final DatabaseErrorHandler mErrorHandler;
+ private final int mLookasideSlotSize;
+ private final int mLookasideSlotCount;
+
+ private OpenParams(int openFlags, CursorFactory cursorFactory,
+ DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount) {
+ mOpenFlags = openFlags;
+ mCursorFactory = cursorFactory;
+ mErrorHandler = errorHandler;
+ mLookasideSlotSize = lookasideSlotSize;
+ mLookasideSlotCount = lookasideSlotCount;
+ }
+
+ /**
+ * Returns size in bytes of each lookaside slot or -1 if not set.
+ *
+ * @see Builder#setLookasideConfig(int, int)
+ */
+ @IntRange(from = -1)
+ public int getLookasideSlotSize() {
+ return mLookasideSlotSize;
+ }
+
+ /**
+ * Returns total number of lookaside memory slots per database connection or -1 if not
+ * set.
+ *
+ * @see Builder#setLookasideConfig(int, int)
+ */
+ @IntRange(from = -1)
+ public int getLookasideSlotCount() {
+ return mLookasideSlotCount;
+ }
+
+ /**
+ * Returns flags to control database access mode
+ *
+ * @see Builder#setOpenFlags(int)
+ */
+ @DatabaseOpenFlags
+ public int getOpenFlags() {
+ return mOpenFlags;
+ }
+
+ /**
+ * Returns an optional factory class that is called to instantiate a cursor when query
+ * is called
+ *
+ * @see Builder#setCursorFactory(CursorFactory)
+ */
+ @Nullable
+ public CursorFactory getCursorFactory() {
+ return mCursorFactory;
+ }
+
+ /**
+ * Returns handler for database corruption errors
+ *
+ * @see Builder#setErrorHandler(DatabaseErrorHandler)
+ */
+ @Nullable
+ public DatabaseErrorHandler getErrorHandler() {
+ return mErrorHandler;
+ }
+
+ /**
+ * Creates a new instance of builder {@link Builder#Builder(OpenParams) initialized} with
+ * {@code this} parameters.
+ * @hide
+ */
+ @NonNull
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Builder for {@link OpenParams}.
+ */
+ public static final class Builder {
+ private int mLookasideSlotSize = -1;
+ private int mLookasideSlotCount = -1;
+ private int mOpenFlags;
+ private CursorFactory mCursorFactory;
+ private DatabaseErrorHandler mErrorHandler;
+
+ public Builder() {
+ }
+
+ public Builder(OpenParams params) {
+ mLookasideSlotSize = params.mLookasideSlotSize;
+ mLookasideSlotCount = params.mLookasideSlotCount;
+ mOpenFlags = params.mOpenFlags;
+ mCursorFactory = params.mCursorFactory;
+ mErrorHandler = params.mErrorHandler;
+ }
+
+ /**
+ * Configures
+ * <a href="https://sqlite.org/malloc.html#lookaside">lookaside memory allocator</a>
+ *
+ * <p>SQLite default settings will be used, if this method isn't called.
+ * Use {@code setLookasideConfig(0,0)} to disable lookaside
+ *
+ * @param slotSize The size in bytes of each lookaside slot.
+ * @param slotCount The total number of lookaside memory slots per database connection.
+ */
+ public Builder setLookasideConfig(@IntRange(from = 0) final int slotSize,
+ @IntRange(from = 0) final int slotCount) {
+ Preconditions.checkArgument(slotSize >= 0,
+ "lookasideSlotCount cannot be negative");
+ Preconditions.checkArgument(slotCount >= 0,
+ "lookasideSlotSize cannot be negative");
+ Preconditions.checkArgument(
+ (slotSize > 0 && slotCount > 0) || (slotCount == 0 && slotSize == 0),
+ "Invalid configuration: " + slotSize + ", " + slotCount);
+
+ mLookasideSlotSize = slotSize;
+ mLookasideSlotCount = slotCount;
+ return this;
+ }
+
+ /**
+ * Returns true if {@link #ENABLE_WRITE_AHEAD_LOGGING} flag is set
+ * @hide
+ */
+ public boolean isWriteAheadLoggingEnabled() {
+ return (mOpenFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ }
+
+ /**
+ * Sets flags to control database access mode
+ * @param openFlags The new flags to set
+ * @see #OPEN_READWRITE
+ * @see #OPEN_READONLY
+ * @see #CREATE_IF_NECESSARY
+ * @see #NO_LOCALIZED_COLLATORS
+ * @see #ENABLE_WRITE_AHEAD_LOGGING
+ * @return same builder instance for chaining multiple calls into a single statement
+ */
+ @NonNull
+ public Builder setOpenFlags(@DatabaseOpenFlags int openFlags) {
+ mOpenFlags = openFlags;
+ return this;
+ }
+
+ /**
+ * Adds flags to control database access mode
+ *
+ * @param openFlags The new flags to add
+ * @return same builder instance for chaining multiple calls into a single statement
+ */
+ @NonNull
+ public Builder addOpenFlags(@DatabaseOpenFlags int openFlags) {
+ mOpenFlags |= openFlags;
+ return this;
+ }
+
+ /**
+ * Removes database access mode flags
+ *
+ * @param openFlags Flags to remove
+ * @return same builder instance for chaining multiple calls into a single statement
+ */
+ @NonNull
+ public Builder removeOpenFlags(@DatabaseOpenFlags int openFlags) {
+ mOpenFlags &= ~openFlags;
+ return this;
+ }
+
+ /**
+ * Sets {@link #ENABLE_WRITE_AHEAD_LOGGING} flag if {@code enabled} is {@code true},
+ * unsets otherwise
+ * @hide
+ */
+ public void setWriteAheadLoggingEnabled(boolean enabled) {
+ if (enabled) {
+ addOpenFlags(ENABLE_WRITE_AHEAD_LOGGING);
+ } else {
+ removeOpenFlags(ENABLE_WRITE_AHEAD_LOGGING);
+ }
+ }
+
+ /**
+ * Set an optional factory class that is called to instantiate a cursor when query
+ * is called.
+ *
+ * @param cursorFactory instance
+ * @return same builder instance for chaining multiple calls into a single statement
+ */
+ @NonNull
+ public Builder setCursorFactory(@Nullable CursorFactory cursorFactory) {
+ mCursorFactory = cursorFactory;
+ return this;
+ }
+
+
+ /**
+ * Sets {@link DatabaseErrorHandler} object to handle db corruption errors
+ */
+ @NonNull
+ public Builder setErrorHandler(@Nullable DatabaseErrorHandler errorHandler) {
+ mErrorHandler = errorHandler;
+ return this;
+ }
+
+ /**
+ * Creates an instance of {@link OpenParams} with the options that were previously set
+ * on this builder
+ */
+ @NonNull
+ public OpenParams build() {
+ return new OpenParams(mOpenFlags, mCursorFactory, mErrorHandler, mLookasideSlotSize,
+ mLookasideSlotCount);
+ }
+ }
+ }
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"OPEN_", "CREATE_", "NO_", "ENABLE_"}, value = {
+ OPEN_READWRITE,
+ OPEN_READONLY,
+ CREATE_IF_NECESSARY,
+ NO_LOCALIZED_COLLATORS,
+ ENABLE_WRITE_AHEAD_LOGGING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DatabaseOpenFlags {}
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 549ab90..1435c83 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -90,6 +90,20 @@
new ArrayList<SQLiteCustomFunction>();
/**
+ * The size in bytes of each lookaside slot
+ *
+ * <p>If negative, the default lookaside configuration will be used
+ */
+ public int lookasideSlotSize;
+
+ /**
+ * The total number of lookaside memory slots per database connection
+ *
+ * <p>If negative, the default lookaside configuration will be used
+ */
+ public int lookasideSlotCount;
+
+ /**
* Creates a database configuration with the required parameters for opening a
* database and default values for all other parameters.
*
@@ -108,6 +122,8 @@
// Set default values for optional parameters.
maxSqlCacheSize = 25;
locale = Locale.getDefault();
+ lookasideSlotSize = -1;
+ lookasideSlotCount = -1;
}
/**
@@ -146,6 +162,8 @@
foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled;
customFunctions.clear();
customFunctions.addAll(other.customFunctions);
+ lookasideSlotSize = other.lookasideSlotSize;
+ lookasideSlotCount = other.lookasideSlotCount;
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index bb8d9ff..44a72dd 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -16,10 +16,14 @@
package android.database.sqlite;
+import android.annotation.IntRange;
import android.content.Context;
import android.database.DatabaseErrorHandler;
+import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.os.FileUtils;
import android.util.Log;
+
import java.io.File;
/**
@@ -43,24 +47,14 @@
public abstract class SQLiteOpenHelper {
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
- // When true, getReadableDatabase returns a read-only database if it is just being opened.
- // The database handle is reopened in read/write mode when getWritableDatabase is called.
- // We leave this behavior disabled in production because it is inefficient and breaks
- // many applications. For debugging purposes it can be useful to turn on strict
- // read-only semantics to catch applications that call getReadableDatabase when they really
- // wanted getWritableDatabase.
- private static final boolean DEBUG_STRICT_READONLY = false;
-
private final Context mContext;
private final String mName;
- private final CursorFactory mFactory;
private final int mNewVersion;
private final int mMinimumSupportedVersion;
private SQLiteDatabase mDatabase;
private boolean mIsInitializing;
- private boolean mEnableWriteAheadLogging;
- private final DatabaseErrorHandler mErrorHandler;
+ private final SQLiteDatabase.OpenParams.Builder mOpenParamsBuilder;
/**
* Create a helper object to create, open, and/or manage a database.
@@ -130,10 +124,12 @@
mContext = context;
mName = name;
- mFactory = factory;
mNewVersion = version;
- mErrorHandler = errorHandler;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
+ mOpenParamsBuilder = new SQLiteDatabase.OpenParams.Builder();
+ mOpenParamsBuilder.setCursorFactory(factory);
+ mOpenParamsBuilder.setErrorHandler(errorHandler);
+ mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
}
/**
@@ -157,7 +153,7 @@
*/
public void setWriteAheadLoggingEnabled(boolean enabled) {
synchronized (this) {
- if (mEnableWriteAheadLogging != enabled) {
+ if (mOpenParamsBuilder.isWriteAheadLoggingEnabled() != enabled) {
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
if (enabled) {
mDatabase.enableWriteAheadLogging();
@@ -165,12 +161,36 @@
mDatabase.disableWriteAheadLogging();
}
}
- mEnableWriteAheadLogging = enabled;
+ mOpenParamsBuilder.setWriteAheadLoggingEnabled(enabled);
}
}
}
/**
+ * Configures <a href="https://sqlite.org/malloc.html#lookaside">lookaside memory allocator</a>
+ *
+ * <p>This method should be called from the constructor of the subclass,
+ * before opening the database, since lookaside memory configuration can only be changed
+ * when no connection is using it
+ *
+ * <p>SQLite default settings will be used, if this method isn't called.
+ * Use {@code setLookasideConfig(0,0)} to disable lookaside
+ *
+ * @param slotSize The size in bytes of each lookaside slot.
+ * @param slotCount The total number of lookaside memory slots per database connection.
+ */
+ public void setLookasideConfig(@IntRange(from = 0) final int slotSize,
+ @IntRange(from = 0) final int slotCount) {
+ synchronized (this) {
+ if (mDatabase != null && mDatabase.isOpen()) {
+ throw new IllegalStateException(
+ "Lookaside memory config cannot be changed after opening the database");
+ }
+ mOpenParamsBuilder.setLookasideConfig(slotSize, slotCount);
+ }
+ }
+
+ /**
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
* {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
@@ -243,27 +263,22 @@
db.reopenReadWrite();
}
} else if (mName == null) {
- db = SQLiteDatabase.create(null);
+ db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
} else {
+ final String path = mContext.getDatabasePath(mName).getPath();
+ SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
try {
- if (DEBUG_STRICT_READONLY && !writable) {
- final String path = mContext.getDatabasePath(mName).getPath();
- db = SQLiteDatabase.openDatabase(path, mFactory,
- SQLiteDatabase.OPEN_READONLY, mErrorHandler);
- } else {
- db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
- Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
- mFactory, mErrorHandler);
- }
- } catch (SQLiteException ex) {
+ db = SQLiteDatabase.openDatabase(path, params);
+ // Keep pre-O-MR1 behavior by resetting file permissions to 660
+ setFilePermissionsForDb(path);
+ } catch (SQLException ex) {
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
- final String path = mContext.getDatabasePath(mName).getPath();
- db = SQLiteDatabase.openDatabase(path, mFactory,
- SQLiteDatabase.OPEN_READONLY, mErrorHandler);
+ params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
+ db = SQLiteDatabase.openDatabase(path, params);
}
}
@@ -323,6 +338,11 @@
}
}
+ private static void setFilePermissionsForDb(String dbPath) {
+ int perms = FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP;
+ FileUtils.setPermissions(dbPath, perms, -1, -1);
+ }
+
/**
* Close any open database object.
*/
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index bcc3bb0..62240ea 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -112,7 +112,8 @@
static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
- jstring labelStr, jboolean enableTrace, jboolean enableProfile) {
+ jstring labelStr, jboolean enableTrace, jboolean enableProfile, jint lookasideSz,
+ jint lookasideCnt) {
int sqliteFlags;
if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) {
sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
@@ -137,6 +138,16 @@
return 0;
}
+ if (lookasideSz >= 0 && lookasideCnt >= 0) {
+ int err = sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE, NULL, lookasideSz, lookasideCnt);
+ if (err != SQLITE_OK) {
+ ALOGE("sqlite3_db_config(..., %d, %d) failed: %d", lookasideSz, lookasideCnt, err);
+ throw_sqlite3_exception(env, db, "Cannot set lookaside");
+ sqlite3_close(db);
+ return 0;
+ }
+ }
+
// Check that the database is really read/write when that is what we asked for.
if ((sqliteFlags & SQLITE_OPEN_READWRITE) && sqlite3_db_readonly(db, NULL)) {
throw_sqlite3_exception(env, db, "Could not open the database in read/write mode.");
@@ -789,7 +800,7 @@
static const JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
- { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZ)J",
+ { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZII)J",
(void*)nativeOpen },
{ "nativeClose", "(J)V",
(void*)nativeClose },
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index c7cb43d..7800f4a 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -18,11 +18,12 @@
import static android.database.DatabaseUtils.InsertHelper.TABLE_INFO_PRAGMA_COLUMNNAME_INDEX;
import static android.database.DatabaseUtils.InsertHelper.TABLE_INFO_PRAGMA_DEFAULT_INDEX;
+
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteException;
-import android.os.Handler;
import android.os.Parcel;
import android.test.AndroidTestCase;
import android.test.PerformanceTestCase;
@@ -40,6 +41,9 @@
import java.util.List;
import java.util.Locale;
+/**
+ * Usage: bit FrameworksCoreTests:android.database.DatabaseGeneralTest
+ */
public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceTestCase {
private static final String TAG = "DatabaseGeneralTest";
@@ -68,7 +72,7 @@
@Override
protected void tearDown() throws Exception {
mDatabase.close();
- mDatabaseFile.delete();
+ SQLiteDatabase.deleteDatabase(mDatabaseFile);
super.tearDown();
}
@@ -1044,6 +1048,52 @@
}
}
+ @SmallTest
+ public void testOpenDatabaseLookasideConfig() {
+ // First check that lookaside is active
+ verifyLookasideStats(false);
+ // Reopen test db with lookaside disabled
+ mDatabase.close();
+ SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+ .setLookasideConfig(0, 0).build();
+ mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), params);
+ verifyLookasideStats(true);
+ }
+
+ @SmallTest
+ public void testOpenParamsSetLookasideConfigValidation() {
+ try {
+ SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+ .setLookasideConfig(-1, 0).build();
+ fail("Negative slot size should be rejected");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder()
+ .setLookasideConfig(0, -10).build();
+ fail("Negative slot count should be rejected");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ void verifyLookasideStats(boolean expectDisabled) {
+ boolean dbStatFound = false;
+ SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
+ for (SQLiteDebug.DbStats dbStat : info.dbStats) {
+ if (dbStat.dbName.endsWith(mDatabaseFile.getName())) {
+ dbStatFound = true;
+ Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
+ if (expectDisabled) {
+ assertTrue("lookaside slots count should be zero", dbStat.lookaside == 0);
+ } else {
+ assertTrue("lookaside slots count should be greater than zero",
+ dbStat.lookaside > 0);
+ }
+ }
+ }
+ assertTrue("No dbstat found for " + mDatabaseFile.getName(), dbStatFound);
+ }
+
@LargeTest
public void testDefaultDatabaseErrorHandler() {
DefaultDatabaseErrorHandler errorHandler = new DefaultDatabaseErrorHandler();
diff --git a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
new file mode 100644
index 0000000..75eeb93
--- /dev/null
+++ b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabaseConfiguration;
+import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link SQLiteOpenHelper}
+ *
+ * <p>Run with: bit FrameworksCoreTests:android.database.SQLiteOpenHelperTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SQLiteOpenHelperTest {
+ private static final String TAG = "SQLiteOpenHelperTest";
+
+ private TestHelper mTestHelper;
+ private Context mContext;
+ private List<SQLiteOpenHelper> mHelpersToClose;
+
+ private static class TestHelper extends SQLiteOpenHelper {
+ TestHelper(Context context) { // In-memory
+ super(context, null, null, 1);
+ }
+
+ TestHelper(Context context, String name) {
+ super(context, name, null, 1);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ }
+ }
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getContext();
+ mTestHelper = new TestHelper(mContext, "openhelper_test");
+ mHelpersToClose = new ArrayList<>();
+ mHelpersToClose.add(mTestHelper);
+ }
+
+ @After
+ public void teardown() {
+ for (SQLiteOpenHelper helper : mHelpersToClose) {
+ try {
+ helper.close();
+ if (mTestHelper.getDatabaseName() != null) {
+ SQLiteDatabase.deleteDatabase(
+ mContext.getDatabasePath(mTestHelper.getDatabaseName()));
+ }
+ } catch (RuntimeException ex) {
+ Log.w(TAG, "Error occured when closing db helper " + helper, ex);
+ }
+ }
+ }
+
+ @Test
+ public void testLookasideDefault() throws Exception {
+ assertNotNull(mTestHelper.getWritableDatabase());
+ verifyLookasideStats(false);
+ }
+
+ @Test
+ public void testLookasideDisabled() throws Exception {
+ mTestHelper.setLookasideConfig(0, 0);
+ assertNotNull(mTestHelper.getWritableDatabase());
+ verifyLookasideStats(true);
+ }
+
+ @Test
+ public void testInMemoryLookasideDisabled() throws Exception {
+ TestHelper memHelper = new TestHelper(mContext);
+ mHelpersToClose.add(memHelper);
+ memHelper.setLookasideConfig(0, 0);
+ assertNotNull(memHelper.getWritableDatabase());
+ verifyLookasideStats(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, true);
+ }
+
+ @Test
+ public void testInMemoryLookasideDefault() throws Exception {
+ TestHelper memHelper = new TestHelper(mContext);
+ mHelpersToClose.add(memHelper);
+ assertNotNull(memHelper.getWritableDatabase());
+ verifyLookasideStats(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, false);
+ }
+
+ @Test
+ public void testSetLookasideConfigValidation() {
+ try {
+ mTestHelper.setLookasideConfig(-1, 0);
+ fail("Negative slot size should be rejected");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ mTestHelper.setLookasideConfig(0, -10);
+ fail("Negative slot count should be rejected");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ mTestHelper.setLookasideConfig(1, 0);
+ fail("Illegal config should be rejected");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ mTestHelper.setLookasideConfig(0, 1);
+ fail("Illegal config should be rejected");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private void verifyLookasideStats(boolean expectDisabled) {
+ verifyLookasideStats(mTestHelper.getDatabaseName(), expectDisabled);
+ }
+
+ private static void verifyLookasideStats(String dbName, boolean expectDisabled) {
+ boolean dbStatFound = false;
+ SQLiteDebug.PagerStats info = SQLiteDebug.getDatabaseInfo();
+ for (SQLiteDebug.DbStats dbStat : info.dbStats) {
+ if (dbStat.dbName.endsWith(dbName)) {
+ dbStatFound = true;
+ Log.i(TAG, "Lookaside for " + dbStat.dbName + " " + dbStat.lookaside);
+ if (expectDisabled) {
+ assertTrue("lookaside slots count should be zero", dbStat.lookaside == 0);
+ } else {
+ assertTrue("lookaside slots count should be greater than zero",
+ dbStat.lookaside > 0);
+ }
+ }
+ }
+ assertTrue("No dbstat found for " + dbName, dbStatFound);
+ }
+}