Merge "Support for lookaside configuration params"

This commit is contained in:
Fyodor Kupolov
2017-06-30 16:47:35 +00:00
committed by Android (Google) Code Review
10 changed files with 672 additions and 52 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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.
*
* <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 {}
}

View File

@@ -89,6 +89,20 @@ public final class SQLiteDatabaseConfiguration {
public final ArrayList<SQLiteCustomFunction> customFunctions =
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;
}
/**

View File

@@ -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 <a href="https://sqlite.org/malloc.html#lookaside">lookaside memory allocator</a>
*
* <p>This method should be called from the constructor of the subclass,
* before opening the database, since lookaside memory configuration can only be changed
* when no connection is using it
*
* <p>SQLite default settings will be used, if this method isn't called.
* Use {@code setLookasideConfig(0,0)} to disable lookaside
*
* @param slotSize The size in bytes of each lookaside slot.
* @param slotCount The total number of lookaside memory slots per database connection.
*/
public void setLookasideConfig(@IntRange(from = 0) final int slotSize,
@IntRange(from = 0) final int slotCount) {
synchronized (this) {
if (mDatabase != null && mDatabase.isOpen()) {
throw new IllegalStateException(
"Lookaside memory config cannot be changed after opening the database");
}
mOpenParamsBuilder.setLookasideConfig(slotSize, slotCount);
}
}
/**
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
@@ -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.
*/

View File

@@ -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 },

View File

@@ -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();

View File

@@ -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);
}
}