diff --git a/api/current.txt b/api/current.txt index 0bda5706ff137..02c313cd49266 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 12d3d9697e706..ca5898670a40a 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 6d401e1758fba..08084b9a61f25 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 34a9523fc104c..f894f0536b522 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 fe849b8a99cf5..7406b3fd1f283 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,10 +676,29 @@ 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,12 +855,28 @@ 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. + * + *
Sets the locale of the database to the the system's current locale. + * Call {@link #setLocale} if you would like something else.
+ * @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 + * lookaside memory allocator + * + *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 549ab902fdd1f..1435c83c53dc0 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -89,6 +89,20 @@ public final class SQLiteDatabaseConfiguration {
public final ArrayList If negative, the default lookaside configuration will be used
+ */
+ public int lookasideSlotSize;
+
+ /**
+ * The total number of lookaside memory slots per database connection
+ *
+ * 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 bb8d9ffa679bf..44a72ddd1f7a8 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,11 +161,35 @@ public abstract class SQLiteOpenHelper {
mDatabase.disableWriteAheadLogging();
}
}
- mEnableWriteAheadLogging = enabled;
+ mOpenParamsBuilder.setWriteAheadLoggingEnabled(enabled);
}
}
}
+ /**
+ * Configures lookaside memory allocator
+ *
+ * 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
+ *
+ * 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
@@ -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 bcc3bb09b69dc..62240ea878070 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 c7cb43d3dbc04..7800f4ab0598f 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 0000000000000..75eeb93f5b9a4
--- /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}
+ *
+ * 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