diff options
| author | 2017-06-30 16:47:35 +0000 | |
|---|---|---|
| committer | 2017-06-30 16:47:35 +0000 | |
| commit | db0da015de01caf8e2d428121dfb0a1fc3b48607 (patch) | |
| tree | fa62b817dc50d7d6e4676c67960a3317b7848763 | |
| parent | a89a8cdef90a0ff3bff06ba52814ddf2fd593228 (diff) | |
| parent | d3b0c7e7e23c4f1d8db5a084befe4653dcb5e1d5 (diff) | |
Merge "Support for lookaside configuration params"
| -rw-r--r-- | api/current.txt | 23 | ||||
| -rw-r--r-- | api/system-current.txt | 23 | ||||
| -rw-r--r-- | api/test-current.txt | 23 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteConnection.java | 15 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteDatabase.java | 304 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java | 18 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteOpenHelper.java | 78 | ||||
| -rw-r--r-- | core/jni/android_database_SQLiteConnection.cpp | 15 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/database/DatabaseGeneralTest.java | 54 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java | 171 |
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); + } +} |