summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Fyodor Kupolov <fkupolov@google.com> 2017-06-30 16:47:35 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2017-06-30 16:47:35 +0000
commitdb0da015de01caf8e2d428121dfb0a1fc3b48607 (patch)
treefa62b817dc50d7d6e4676c67960a3317b7848763
parenta89a8cdef90a0ff3bff06ba52814ddf2fd593228 (diff)
parentd3b0c7e7e23c4f1d8db5a084befe4653dcb5e1d5 (diff)
Merge "Support for lookaside configuration params"
-rw-r--r--api/current.txt23
-rw-r--r--api/system-current.txt23
-rw-r--r--api/test-current.txt23
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java15
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java304
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java18
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java78
-rw-r--r--core/jni/android_database_SQLiteConnection.cpp15
-rw-r--r--core/tests/coretests/src/android/database/DatabaseGeneralTest.java54
-rw-r--r--core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java171
10 files changed, 672 insertions, 52 deletions
diff --git a/api/current.txt b/api/current.txt
index 0bda5706ff13..02c313cd4926 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11872,6 +11872,7 @@ package android.database.sqlite {
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();
@@ -11901,6 +11902,7 @@ package android.database.sqlite {
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);
@@ -11951,6 +11953,26 @@ package android.database.sqlite {
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);
@@ -12004,6 +12026,7 @@ package android.database.sqlite {
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 12d3d9697e70..ca5898670a40 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -12659,6 +12659,7 @@ package android.database.sqlite {
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();
@@ -12688,6 +12689,7 @@ package android.database.sqlite {
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);
@@ -12738,6 +12740,26 @@ package android.database.sqlite {
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);
@@ -12791,6 +12813,7 @@ package android.database.sqlite {
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 6d401e1758fb..08084b9a61f2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -11915,6 +11915,7 @@ package android.database.sqlite {
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();
@@ -11944,6 +11945,7 @@ package android.database.sqlite {
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);
@@ -11994,6 +11996,26 @@ package android.database.sqlite {
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);
@@ -12047,6 +12069,7 @@ package android.database.sqlite {
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 34a9523fc104..f894f0536b52 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.Log;
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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
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 fe849b8a99cf..7406b3fd1f28 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.Log;
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 final class SQLiteDatabase extends SQLiteClosable {
*/
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 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @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 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @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 @@ public final class SQLiteDatabase extends SQLiteClosable {
/**
* 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 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @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 @@ public final class SQLiteDatabase extends SQLiteClosable {
* 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 final class SQLiteDatabase extends SQLiteClosable {
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 549ab902fdd1..1435c83c53dc 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -90,6 +90,20 @@ public final class SQLiteDatabaseConfiguration {
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 @@ public final class SQLiteDatabaseConfiguration {
// Set default values for optional parameters.
maxSqlCacheSize = 25;
locale = Locale.getDefault();
+ lookasideSlotSize = -1;
+ lookasideSlotCount = -1;
}
/**
@@ -146,6 +162,8 @@ public final class SQLiteDatabaseConfiguration {
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 bb8d9ffa679b..44a72ddd1f7a 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 @@ import java.io.File;
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 @@ public abstract class SQLiteOpenHelper {
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 abstract class SQLiteOpenHelper {
*/
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,8 +161,32 @@ public abstract class SQLiteOpenHelper {
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);
}
}
@@ -243,27 +263,22 @@ public abstract class SQLiteOpenHelper {
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 @@ public abstract class SQLiteOpenHelper {
}
}
+ 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 bcc3bb09b69d..62240ea87807 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -112,7 +112,8 @@ static int sqliteProgressHandlerCallback(void* data) {
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 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla
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 void nativeResetCancel(JNIEnv* env, jobject clazz, jlong connectionPtr,
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 c7cb43d3dbc0..7800f4ab0598 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -18,11 +18,12 @@ package android.database;
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.Arrays;
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 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT
@Override
protected void tearDown() throws Exception {
mDatabase.close();
- mDatabaseFile.delete();
+ SQLiteDatabase.deleteDatabase(mDatabaseFile);
super.tearDown();
}
@@ -1044,6 +1048,52 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT
}
}
+ @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 000000000000..75eeb93f5b9a
--- /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);
+ }
+}