mirror of https://github.com/M66B/FairEmail.git
parent
1a359431c6
commit
70a2af170f
@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.arch.core.util.Function;
|
||||
import androidx.room.util.SneakyThrow;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* AutoCloser is responsible for automatically opening (using
|
||||
* delegateOpenHelper) and closing (on a timer started when there are no remaining references) a
|
||||
* SupportSqliteDatabase.
|
||||
*
|
||||
* It is important to ensure that the ref count is incremented when using a returned database.
|
||||
*/
|
||||
final class AutoCloser {
|
||||
|
||||
@Nullable
|
||||
private SupportSQLiteOpenHelper mDelegateOpenHelper = null;
|
||||
|
||||
@NonNull
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// Package private for access from mAutoCloser
|
||||
@Nullable
|
||||
Runnable mOnAutoCloseCallback = null;
|
||||
|
||||
// Package private for access from mAutoCloser
|
||||
@NonNull
|
||||
final Object mLock = new Object();
|
||||
|
||||
// Package private for access from mAutoCloser
|
||||
final long mAutoCloseTimeoutInMs;
|
||||
|
||||
// Package private for access from mExecuteAutoCloser
|
||||
@NonNull
|
||||
final Executor mExecutor;
|
||||
|
||||
// Package private for access from mAutoCloser
|
||||
@GuardedBy("mLock")
|
||||
int mRefCount = 0;
|
||||
|
||||
// Package private for access from mAutoCloser
|
||||
@GuardedBy("mLock")
|
||||
long mLastDecrementRefCountTimeStamp = SystemClock.uptimeMillis();
|
||||
|
||||
// The unwrapped SupportSqliteDatabase
|
||||
// Package private for access from mAutoCloser
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
SupportSQLiteDatabase mDelegateDatabase;
|
||||
|
||||
private boolean mManuallyClosed = false;
|
||||
|
||||
private final Runnable mExecuteAutoCloser = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mExecutor.execute(mAutoCloser);
|
||||
}
|
||||
};
|
||||
|
||||
// Package private for access from mExecuteAutoCloser
|
||||
@NonNull
|
||||
final Runnable mAutoCloser = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (mLock) {
|
||||
if (SystemClock.uptimeMillis() - mLastDecrementRefCountTimeStamp
|
||||
< mAutoCloseTimeoutInMs) {
|
||||
// An increment + decrement beat us to closing the db. We
|
||||
// will not close the database, and there should be at least
|
||||
// one more auto-close scheduled.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRefCount != 0) {
|
||||
// An increment beat us to closing the db. We don't close the
|
||||
// db, and another closer will be scheduled once the ref
|
||||
// count is decremented.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOnAutoCloseCallback != null) {
|
||||
mOnAutoCloseCallback.run();
|
||||
} else {
|
||||
throw new IllegalStateException("mOnAutoCloseCallback is null but it should"
|
||||
+ " have been set before use. Please file a bug "
|
||||
+ "against Room at: https://issuetracker.google"
|
||||
+ ".com/issues/new?component=413107&template=1096568");
|
||||
}
|
||||
|
||||
if (mDelegateDatabase != null && mDelegateDatabase.isOpen()) {
|
||||
try {
|
||||
mDelegateDatabase.close();
|
||||
} catch (IOException e) {
|
||||
SneakyThrow.reThrow(e);
|
||||
}
|
||||
mDelegateDatabase = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Construct an AutoCloser.
|
||||
*
|
||||
* @param autoCloseTimeoutAmount time for auto close timer
|
||||
* @param autoCloseTimeUnit time unit for autoCloseTimeoutAmount
|
||||
* @param autoCloseExecutor the executor on which the auto close operation will happen
|
||||
*/
|
||||
AutoCloser(long autoCloseTimeoutAmount,
|
||||
@NonNull TimeUnit autoCloseTimeUnit,
|
||||
@NonNull Executor autoCloseExecutor) {
|
||||
mAutoCloseTimeoutInMs = autoCloseTimeUnit.toMillis(autoCloseTimeoutAmount);
|
||||
mExecutor = autoCloseExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since we need to construct the AutoCloser in the RoomDatabase.Builder, we need to set the
|
||||
* delegateOpenHelper after construction.
|
||||
*
|
||||
* @param delegateOpenHelper the open helper that is used to create
|
||||
* new SupportSqliteDatabases
|
||||
*/
|
||||
public void init(@NonNull SupportSQLiteOpenHelper delegateOpenHelper) {
|
||||
if (mDelegateOpenHelper != null) {
|
||||
Log.e(Room.LOG_TAG, "AutoCloser initialized multiple times. Please file a bug against"
|
||||
+ " room at: https://issuetracker.google"
|
||||
+ ".com/issues/new?component=413107&template=1096568");
|
||||
return;
|
||||
}
|
||||
this.mDelegateOpenHelper = delegateOpenHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a ref counting function. The function will receive an unwrapped open database and
|
||||
* this database will stay open until at least after function returns. If there are no more
|
||||
* references in use for the db once function completes, an auto close operation will be
|
||||
* scheduled.
|
||||
*/
|
||||
@Nullable
|
||||
public <V> V executeRefCountingFunction(@NonNull Function<SupportSQLiteDatabase, V> function) {
|
||||
try {
|
||||
SupportSQLiteDatabase db = incrementCountAndEnsureDbIsOpen();
|
||||
return function.apply(db);
|
||||
} finally {
|
||||
decrementCountAndScheduleClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that autoCloser is no longer running and confirms that mDelegateDatabase is set
|
||||
* and open. mDelegateDatabase will not be auto closed until
|
||||
* decrementRefCountAndScheduleClose is called. decrementRefCountAndScheduleClose must be
|
||||
* called once for each call to incrementCountAndEnsureDbIsOpen.
|
||||
*
|
||||
* If this throws an exception, decrementCountAndScheduleClose must still be called!
|
||||
*
|
||||
* @return the *unwrapped* SupportSQLiteDatabase.
|
||||
*/
|
||||
@NonNull
|
||||
public SupportSQLiteDatabase incrementCountAndEnsureDbIsOpen() {
|
||||
//TODO(rohitsat): avoid synchronized(mLock) when possible. We should be able to avoid it
|
||||
// when refCount is not hitting zero or if there is no auto close scheduled if we use
|
||||
// Atomics.
|
||||
synchronized (mLock) {
|
||||
// If there is a scheduled autoclose operation, we should remove it from the handler.
|
||||
mHandler.removeCallbacks(mExecuteAutoCloser);
|
||||
|
||||
mRefCount++;
|
||||
|
||||
if (mManuallyClosed) {
|
||||
throw new IllegalStateException("Attempting to open already closed database.");
|
||||
}
|
||||
|
||||
if (mDelegateDatabase != null && mDelegateDatabase.isOpen()) {
|
||||
return mDelegateDatabase;
|
||||
}
|
||||
|
||||
// Get the database while holding `mLock` so no other threads try to create it or
|
||||
// destroy it.
|
||||
if (mDelegateOpenHelper != null) {
|
||||
mDelegateDatabase = mDelegateOpenHelper.getWritableDatabase();
|
||||
} else {
|
||||
throw new IllegalStateException("AutoCloser has not been initialized. Please file "
|
||||
+ "a bug against Room at: "
|
||||
+ "https://issuetracker.google.com/issues/new?component=413107&template=1096568");
|
||||
}
|
||||
|
||||
return mDelegateDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the ref count and schedules a close if there are no other references to the db.
|
||||
* This must only be called after a corresponding incrementCountAndEnsureDbIsOpen call.
|
||||
*/
|
||||
public void decrementCountAndScheduleClose() {
|
||||
//TODO(rohitsat): avoid synchronized(mLock) when possible
|
||||
synchronized (mLock) {
|
||||
if (mRefCount <= 0) {
|
||||
throw new IllegalStateException("ref count is 0 or lower but we're supposed to "
|
||||
+ "decrement");
|
||||
}
|
||||
|
||||
// decrement refCount
|
||||
mRefCount--;
|
||||
|
||||
// if refcount is zero, schedule close operation
|
||||
if (mRefCount == 0) {
|
||||
if (mDelegateDatabase == null) {
|
||||
// No db to close, this can happen due to exceptions when creating db...
|
||||
return;
|
||||
}
|
||||
mHandler.postDelayed(mExecuteAutoCloser, mAutoCloseTimeoutInMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying database. This does not ensure that the database is open; the
|
||||
* caller is responsible for ensuring that the database is open and the ref count is non-zero.
|
||||
*
|
||||
* This is primarily meant for use cases where we don't want to open the database (isOpen) or
|
||||
* we know that the database is already open (KeepAliveCursor).
|
||||
*/
|
||||
@Nullable // Since the db might be closed
|
||||
public SupportSQLiteDatabase getDelegateDatabase() {
|
||||
synchronized (mLock) {
|
||||
return mDelegateDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database if it is still active.
|
||||
*
|
||||
* @throws IOException if an exception is encountered when closing the underlying db.
|
||||
*/
|
||||
public void closeDatabaseIfOpen() throws IOException {
|
||||
synchronized (mLock) {
|
||||
mManuallyClosed = true;
|
||||
|
||||
if (mDelegateDatabase != null) {
|
||||
mDelegateDatabase.close();
|
||||
}
|
||||
mDelegateDatabase = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The auto closer is still active if the database has not been closed. This means that
|
||||
* whether or not the underlying database is closed, when active we will re-open it on the
|
||||
* next access.
|
||||
*
|
||||
* @return a boolean indicating whether the auto closer is still active
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return !mManuallyClosed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current ref count for this auto closer. This is only visible for testing.
|
||||
*
|
||||
* @return current ref count
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public int getRefCountForTest() {
|
||||
synchronized (mLock) {
|
||||
return mRefCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a callback that will be run every time the database is auto-closed. This callback
|
||||
* needs to be lightweight since it is run while holding a lock.
|
||||
*
|
||||
* @param onAutoClose the callback to run
|
||||
*/
|
||||
public void setAutoCloseCallback(Runnable onAutoClose) {
|
||||
mOnAutoCloseCallback = onAutoClose;
|
||||
}
|
||||
}
|
@ -0,0 +1,875 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 androidx.room;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.CharArrayBuffer;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteTransactionListener;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.arch.core.util.Function;
|
||||
import androidx.room.util.SneakyThrow;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
import androidx.sqlite.db.SupportSQLiteQuery;
|
||||
import androidx.sqlite.db.SupportSQLiteStatement;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* A SupportSQLiteOpenHelper that has autoclose enabled for database connections.
|
||||
*/
|
||||
final class AutoClosingRoomOpenHelper implements SupportSQLiteOpenHelper, DelegatingOpenHelper {
|
||||
@NonNull
|
||||
private final SupportSQLiteOpenHelper mDelegateOpenHelper;
|
||||
|
||||
@NonNull
|
||||
private final AutoClosingSupportSQLiteDatabase mAutoClosingDb;
|
||||
|
||||
@NonNull
|
||||
private final AutoCloser mAutoCloser;
|
||||
|
||||
AutoClosingRoomOpenHelper(@NonNull SupportSQLiteOpenHelper supportSQLiteOpenHelper,
|
||||
@NonNull AutoCloser autoCloser) {
|
||||
mDelegateOpenHelper = supportSQLiteOpenHelper;
|
||||
mAutoCloser = autoCloser;
|
||||
autoCloser.init(mDelegateOpenHelper);
|
||||
mAutoClosingDb = new AutoClosingSupportSQLiteDatabase(mAutoCloser);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getDatabaseName() {
|
||||
return mDelegateOpenHelper.getDatabaseName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
public void setWriteAheadLoggingEnabled(boolean enabled) {
|
||||
mDelegateOpenHelper.setWriteAheadLoggingEnabled(enabled);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public SupportSQLiteDatabase getWritableDatabase() {
|
||||
// Note we don't differentiate between writable db and readable db
|
||||
// We try to open the db so the open callbacks run
|
||||
mAutoClosingDb.pokeOpen();
|
||||
return mAutoClosingDb;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public SupportSQLiteDatabase getReadableDatabase() {
|
||||
// Note we don't differentiate between writable db and readable db
|
||||
// We try to open the db so the open callbacks run
|
||||
mAutoClosingDb.pokeOpen();
|
||||
return mAutoClosingDb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
mAutoClosingDb.close();
|
||||
} catch (IOException e) {
|
||||
SneakyThrow.reThrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* package protected to pass it to invalidation tracker...
|
||||
*/
|
||||
@NonNull
|
||||
AutoCloser getAutoCloser() {
|
||||
return this.mAutoCloser;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
SupportSQLiteDatabase getAutoClosingDb() {
|
||||
return this.mAutoClosingDb;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public SupportSQLiteOpenHelper getDelegate() {
|
||||
return mDelegateOpenHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* SupportSQLiteDatabase that also keeps refcounts and autocloses the database
|
||||
*/
|
||||
static final class AutoClosingSupportSQLiteDatabase implements SupportSQLiteDatabase {
|
||||
@NonNull
|
||||
private final AutoCloser mAutoCloser;
|
||||
|
||||
AutoClosingSupportSQLiteDatabase(@NonNull AutoCloser autoCloser) {
|
||||
mAutoCloser = autoCloser;
|
||||
}
|
||||
|
||||
void pokeOpen() {
|
||||
mAutoCloser.executeRefCountingFunction(db -> null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportSQLiteStatement compileStatement(String sql) {
|
||||
return new AutoClosingSupportSqliteStatement(sql, mAutoCloser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransaction() {
|
||||
// We assume that after every successful beginTransaction() call there *must* be a
|
||||
// endTransaction() call.
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
try {
|
||||
db.beginTransaction();
|
||||
} catch (Throwable t) {
|
||||
// Note: we only want to decrement the ref count if the beginTransaction call
|
||||
// fails since there won't be a corresponding endTransaction call.
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransactionNonExclusive() {
|
||||
// We assume that after every successful beginTransaction() call there *must* be a
|
||||
// endTransaction() call.
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
try {
|
||||
db.beginTransactionNonExclusive();
|
||||
} catch (Throwable t) {
|
||||
// Note: we only want to decrement the ref count if the beginTransaction call
|
||||
// fails since there won't be a corresponding endTransaction call.
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
|
||||
// We assume that after every successful beginTransaction() call there *must* be a
|
||||
// endTransaction() call.
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
try {
|
||||
db.beginTransactionWithListener(transactionListener);
|
||||
} catch (Throwable t) {
|
||||
// Note: we only want to decrement the ref count if the beginTransaction call
|
||||
// fails since there won't be a corresponding endTransaction call.
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransactionWithListenerNonExclusive(
|
||||
SQLiteTransactionListener transactionListener) {
|
||||
// We assume that after every successful beginTransaction() call there *will* always
|
||||
// be a corresponding endTransaction() call. Without a corresponding
|
||||
// endTransactionCall we will never close the db.
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
try {
|
||||
db.beginTransactionWithListenerNonExclusive(transactionListener);
|
||||
} catch (Throwable t) {
|
||||
// Note: we only want to decrement the ref count if the beginTransaction call
|
||||
// fails since there won't be a corresponding endTransaction call.
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTransaction() {
|
||||
if (mAutoCloser.getDelegateDatabase() == null) {
|
||||
// This should never happen.
|
||||
throw new IllegalStateException("End transaction called but delegateDb is null");
|
||||
}
|
||||
|
||||
try {
|
||||
mAutoCloser.getDelegateDatabase().endTransaction();
|
||||
} finally {
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransactionSuccessful() {
|
||||
SupportSQLiteDatabase delegate = mAutoCloser.getDelegateDatabase();
|
||||
|
||||
if (delegate == null) {
|
||||
// This should never happen.
|
||||
throw new IllegalStateException("setTransactionSuccessful called but delegateDb "
|
||||
+ "is null");
|
||||
}
|
||||
|
||||
delegate.setTransactionSuccessful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inTransaction() {
|
||||
if (mAutoCloser.getDelegateDatabase() == null) {
|
||||
return false;
|
||||
}
|
||||
return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::inTransaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDbLockedByCurrentThread() {
|
||||
if (mAutoCloser.getDelegateDatabase() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mAutoCloser.executeRefCountingFunction(
|
||||
SupportSQLiteDatabase::isDbLockedByCurrentThread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean yieldIfContendedSafely() {
|
||||
return mAutoCloser.executeRefCountingFunction(
|
||||
SupportSQLiteDatabase::yieldIfContendedSafely);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
|
||||
return mAutoCloser.executeRefCountingFunction(
|
||||
SupportSQLiteDatabase::yieldIfContendedSafely);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(int version) {
|
||||
mAutoCloser.executeRefCountingFunction(db -> {
|
||||
db.setVersion(version);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximumSize() {
|
||||
return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getMaximumSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long setMaximumSize(long numBytes) {
|
||||
return mAutoCloser.executeRefCountingFunction(db -> db.setMaximumSize(numBytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPageSize() {
|
||||
return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getPageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPageSize(long numBytes) {
|
||||
mAutoCloser.executeRefCountingFunction(db -> {
|
||||
db.setPageSize(numBytes);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(String query) {
|
||||
Cursor result;
|
||||
try {
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
result = db.query(query);
|
||||
} catch (Throwable throwable) {
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
return new KeepAliveCursor(result, mAutoCloser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(String query, Object[] bindArgs) {
|
||||
Cursor result;
|
||||
try {
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
result = db.query(query, bindArgs);
|
||||
} catch (Throwable throwable) {
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
return new KeepAliveCursor(result, mAutoCloser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(SupportSQLiteQuery query) {
|
||||
|
||||
Cursor result;
|
||||
try {
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
result = db.query(query);
|
||||
} catch (Throwable throwable) {
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
return new KeepAliveCursor(result, mAutoCloser);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@Override
|
||||
public Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal) {
|
||||
Cursor result;
|
||||
try {
|
||||
SupportSQLiteDatabase db = mAutoCloser.incrementCountAndEnsureDbIsOpen();
|
||||
result = db.query(query, cancellationSignal);
|
||||
} catch (Throwable throwable) {
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
return new KeepAliveCursor(result, mAutoCloser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insert(String table, int conflictAlgorithm, ContentValues values)
|
||||
throws SQLException {
|
||||
return mAutoCloser.executeRefCountingFunction(db -> db.insert(table, conflictAlgorithm,
|
||||
values));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(String table, String whereClause, Object[] whereArgs) {
|
||||
return mAutoCloser.executeRefCountingFunction(
|
||||
db -> db.delete(table, whereClause, whereArgs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(String table, int conflictAlgorithm, ContentValues values,
|
||||
String whereClause, Object[] whereArgs) {
|
||||
return mAutoCloser.executeRefCountingFunction(db -> db.update(table, conflictAlgorithm,
|
||||
values, whereClause, whereArgs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execSQL(String sql) throws SQLException {
|
||||
mAutoCloser.executeRefCountingFunction(db -> {
|
||||
db.execSQL(sql);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execSQL(String sql, Object[] bindArgs) throws SQLException {
|
||||
mAutoCloser.executeRefCountingFunction(db -> {
|
||||
db.execSQL(sql, bindArgs);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::isReadOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
// Get the db without incrementing the reference cause we don't want to open
|
||||
// the db for an isOpen call.
|
||||
SupportSQLiteDatabase localDelegate = mAutoCloser.getDelegateDatabase();
|
||||
|
||||
if (localDelegate == null) {
|
||||
return false;
|
||||
}
|
||||
return localDelegate.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needUpgrade(int newVersion) {
|
||||
return mAutoCloser.executeRefCountingFunction(db -> db.needUpgrade(newVersion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocale(Locale locale) {
|
||||
mAutoCloser.executeRefCountingFunction(db -> {
|
||||
db.setLocale(locale);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxSqlCacheSize(int cacheSize) {
|
||||
mAutoCloser.executeRefCountingFunction(db -> {
|
||||
db.setMaxSqlCacheSize(cacheSize);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeNewApiCall")
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
@Override
|
||||
public void setForeignKeyConstraintsEnabled(boolean enable) {
|
||||
mAutoCloser.executeRefCountingFunction(db -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
db.setForeignKeyConstraintsEnabled(enable);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableWriteAheadLogging() {
|
||||
throw new UnsupportedOperationException("Enable/disable write ahead logging on the "
|
||||
+ "OpenHelper instead of on the database directly.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableWriteAheadLogging() {
|
||||
throw new UnsupportedOperationException("Enable/disable write ahead logging on the "
|
||||
+ "OpenHelper instead of on the database directly.");
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeNewApiCall")
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
@Override
|
||||
public boolean isWriteAheadLoggingEnabled() {
|
||||
return mAutoCloser.executeRefCountingFunction(db -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
return db.isWriteAheadLoggingEnabled();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, String>> getAttachedDbs() {
|
||||
return mAutoCloser.executeRefCountingFunction(SupportSQLiteDatabase::getAttachedDbs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDatabaseIntegrityOk() {
|
||||
return mAutoCloser.executeRefCountingFunction(
|
||||
SupportSQLiteDatabase::isDatabaseIntegrityOk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mAutoCloser.closeDatabaseIfOpen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to keep the db alive until the cursor is closed, so we can't decrement our
|
||||
* reference count until the cursor is closed. The underlying database will not close until
|
||||
* this cursor is closed.
|
||||
*/
|
||||
private static final class KeepAliveCursor implements Cursor {
|
||||
private final Cursor mDelegate;
|
||||
private final AutoCloser mAutoCloser;
|
||||
|
||||
KeepAliveCursor(Cursor delegate, AutoCloser autoCloser) {
|
||||
mDelegate = delegate;
|
||||
mAutoCloser = autoCloser;
|
||||
}
|
||||
|
||||
// close is the only important/changed method here:
|
||||
@Override
|
||||
public void close() {
|
||||
mDelegate.close();
|
||||
mAutoCloser.decrementCountAndScheduleClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return mDelegate.isClosed();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mDelegate.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return mDelegate.getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean move(int offset) {
|
||||
return mDelegate.move(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToPosition(int position) {
|
||||
return mDelegate.moveToPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToFirst() {
|
||||
return mDelegate.moveToFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToLast() {
|
||||
return mDelegate.moveToLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToNext() {
|
||||
return mDelegate.moveToNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToPrevious() {
|
||||
return mDelegate.moveToPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFirst() {
|
||||
return mDelegate.isFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLast() {
|
||||
return mDelegate.isLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeforeFirst() {
|
||||
return mDelegate.isBeforeFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAfterLast() {
|
||||
return mDelegate.isAfterLast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnIndex(String columnName) {
|
||||
return mDelegate.getColumnIndex(columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
|
||||
return mDelegate.getColumnIndexOrThrow(columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex) {
|
||||
return mDelegate.getColumnName(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getColumnNames() {
|
||||
return mDelegate.getColumnNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return mDelegate.getColumnCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBlob(int columnIndex) {
|
||||
return mDelegate.getBlob(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int columnIndex) {
|
||||
return mDelegate.getString(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
|
||||
mDelegate.copyStringToBuffer(columnIndex, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(int columnIndex) {
|
||||
return mDelegate.getShort(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int columnIndex) {
|
||||
return mDelegate.getInt(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int columnIndex) {
|
||||
return mDelegate.getLong(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(int columnIndex) {
|
||||
return mDelegate.getFloat(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int columnIndex) {
|
||||
return mDelegate.getDouble(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType(int columnIndex) {
|
||||
return mDelegate.getType(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull(int columnIndex) {
|
||||
return mDelegate.isNull(columnIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated see Cursor.deactivate
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void deactivate() {
|
||||
mDelegate.deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated see Cursor.requery
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public boolean requery() {
|
||||
return mDelegate.requery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerContentObserver(ContentObserver observer) {
|
||||
mDelegate.registerContentObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterContentObserver(ContentObserver observer) {
|
||||
mDelegate.unregisterContentObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
mDelegate.registerDataSetObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
mDelegate.unregisterDataSetObserver(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotificationUri(ContentResolver cr, Uri uri) {
|
||||
mDelegate.setNotificationUri(cr, uri);
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeNewApiCall")
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
@Override
|
||||
public void setNotificationUris(@NonNull ContentResolver cr,
|
||||
@NonNull List<Uri> uris) {
|
||||
mDelegate.setNotificationUris(cr, uris);
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeNewApiCall")
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public Uri getNotificationUri() {
|
||||
return mDelegate.getNotificationUri();
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeNewApiCall")
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
@Nullable
|
||||
@Override
|
||||
public List<Uri> getNotificationUris() {
|
||||
return mDelegate.getNotificationUris();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getWantsAllOnMoveCalls() {
|
||||
return mDelegate.getWantsAllOnMoveCalls();
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeNewApiCall")
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void setExtras(Bundle extras) {
|
||||
mDelegate.setExtras(extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle getExtras() {
|
||||
return mDelegate.getExtras();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle respond(Bundle extras) {
|
||||
return mDelegate.respond(extras);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We can't close our db if the SupportSqliteStatement is open.
|
||||
*
|
||||
* Each of these that are created need to be registered with RefCounter.
|
||||
*
|
||||
* On auto-close, RefCounter needs to close each of these before closing the db that these
|
||||
* were constructed from.
|
||||
*
|
||||
* Each of the methods here need to get
|
||||
*/
|
||||
//TODO(rohitsat) cache the prepared statement... I'm not sure what the performance implications
|
||||
// are for the way it's done here, but caching the prepared statement would definitely be more
|
||||
// complicated since we need to invalidate any of the PreparedStatements that were created
|
||||
// with this db
|
||||
private static class AutoClosingSupportSqliteStatement implements SupportSQLiteStatement {
|
||||
private final String mSql;
|
||||
private final ArrayList<Object> mBinds = new ArrayList<>();
|
||||
private final AutoCloser mAutoCloser;
|
||||
|
||||
AutoClosingSupportSqliteStatement(
|
||||
String sql, AutoCloser autoCloser) {
|
||||
mSql = sql;
|
||||
mAutoCloser = autoCloser;
|
||||
}
|
||||
|
||||
private <T> T executeSqliteStatementWithRefCount(Function<SupportSQLiteStatement, T> func) {
|
||||
return mAutoCloser.executeRefCountingFunction(
|
||||
db -> {
|
||||
SupportSQLiteStatement statement = db.compileStatement(mSql);
|
||||
doBinds(statement);
|
||||
return func.apply(statement);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void doBinds(SupportSQLiteStatement supportSQLiteStatement) {
|
||||
// Replay the binds
|
||||
for (int i = 0; i < mBinds.size(); i++) {
|
||||
int bindIndex = i + 1; // Bind indices are 1 based so we start at 1 not 0
|
||||
Object bind = mBinds.get(i);
|
||||
if (bind == null) {
|
||||
supportSQLiteStatement.bindNull(bindIndex);
|
||||
} else if (bind instanceof Long) {
|
||||
supportSQLiteStatement.bindLong(bindIndex, (Long) bind);
|
||||
} else if (bind instanceof Double) {
|
||||
supportSQLiteStatement.bindDouble(bindIndex, (Double) bind);
|
||||
} else if (bind instanceof String) {
|
||||
supportSQLiteStatement.bindString(bindIndex, (String) bind);
|
||||
} else if (bind instanceof byte[]) {
|
||||
supportSQLiteStatement.bindBlob(bindIndex, (byte[]) bind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveBinds(int bindIndex, Object value) {
|
||||
int index = bindIndex - 1;
|
||||
if (index >= mBinds.size()) {
|
||||
// Add null entries to the list until we have the desired # of indices
|
||||
for (int i = mBinds.size(); i <= index; i++) {
|
||||
mBinds.add(null);
|
||||
}
|
||||
}
|
||||
mBinds.set(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// Nothing to do here since we re-compile the statement each time.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
executeSqliteStatementWithRefCount(statement -> {
|
||||
statement.execute();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdateDelete() {
|
||||
return executeSqliteStatementWithRefCount(SupportSQLiteStatement::executeUpdateDelete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long executeInsert() {
|
||||
return executeSqliteStatementWithRefCount(SupportSQLiteStatement::executeInsert);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long simpleQueryForLong() {
|
||||
return executeSqliteStatementWithRefCount(SupportSQLiteStatement::simpleQueryForLong);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String simpleQueryForString() {
|
||||
return executeSqliteStatementWithRefCount(SupportSQLiteStatement::simpleQueryForString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index) {
|
||||
saveBinds(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindLong(int index, long value) {
|
||||
saveBinds(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindDouble(int index, double value) {
|
||||
saveBinds(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindString(int index, String value) {
|
||||
saveBinds(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindBlob(int index, byte[] value) {
|
||||
saveBinds(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBindings() {
|
||||
mBinds.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
/**
|
||||
* Factory class for AutoClosingRoomOpenHelper
|
||||
*/
|
||||
final class AutoClosingRoomOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
|
||||
@NonNull
|
||||
private final SupportSQLiteOpenHelper.Factory mDelegate;
|
||||
|
||||
@NonNull
|
||||
private final AutoCloser mAutoCloser;
|
||||
|
||||
AutoClosingRoomOpenHelperFactory(
|
||||
@NonNull SupportSQLiteOpenHelper.Factory factory,
|
||||
@NonNull AutoCloser autoCloser) {
|
||||
mDelegate = factory;
|
||||
mAutoCloser = autoCloser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AutoClosingRoomOpenHelper instances.
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public AutoClosingRoomOpenHelper create(
|
||||
@NonNull SupportSQLiteOpenHelper.Configuration configuration) {
|
||||
return new AutoClosingRoomOpenHelper(mDelegate.create(configuration), mAutoCloser);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
/**
|
||||
* Package private interface for OpenHelpers which delegate to other open helpers.
|
||||
*
|
||||
* TODO(b/175612939): delete this interface once implementations are merged.
|
||||
*/
|
||||
interface DelegatingOpenHelper {
|
||||
|
||||
/**
|
||||
* Returns the delegate open helper (which may itself be a DelegatingOpenHelper) so
|
||||
* configurations on specific instances can be applied.
|
||||
*
|
||||
* @return the delegate
|
||||
*/
|
||||
@NonNull
|
||||
SupportSQLiteOpenHelper getDelegate();
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import androidx.annotation.RequiresOptIn;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* APIs marked with ExperimentalRoomApi are experimental and may change.
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@RequiresOptIn()
|
||||
@interface ExperimentalRoomApi {}
|
@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteTransactionListener;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteQuery;
|
||||
import androidx.sqlite.db.SupportSQLiteStatement;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
|
||||
/**
|
||||
* Implements {@link SupportSQLiteDatabase} for SQLite queries.
|
||||
*/
|
||||
final class QueryInterceptorDatabase implements SupportSQLiteDatabase {
|
||||
|
||||
private final SupportSQLiteDatabase mDelegate;
|
||||
private final RoomDatabase.QueryCallback mQueryCallback;
|
||||
private final Executor mQueryCallbackExecutor;
|
||||
|
||||
QueryInterceptorDatabase(@NonNull SupportSQLiteDatabase supportSQLiteDatabase,
|
||||
@NonNull RoomDatabase.QueryCallback queryCallback, @NonNull Executor
|
||||
queryCallbackExecutor) {
|
||||
mDelegate = supportSQLiteDatabase;
|
||||
mQueryCallback = queryCallback;
|
||||
mQueryCallbackExecutor = queryCallbackExecutor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SupportSQLiteStatement compileStatement(@NonNull String sql) {
|
||||
return new QueryInterceptorStatement(mDelegate.compileStatement(sql),
|
||||
mQueryCallback, sql, mQueryCallbackExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransaction() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery("BEGIN EXCLUSIVE TRANSACTION",
|
||||
Collections.emptyList()));
|
||||
mDelegate.beginTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransactionNonExclusive() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery("BEGIN DEFERRED TRANSACTION",
|
||||
Collections.emptyList()));
|
||||
mDelegate.beginTransactionNonExclusive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransactionWithListener(@NonNull SQLiteTransactionListener
|
||||
transactionListener) {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery("BEGIN EXCLUSIVE TRANSACTION",
|
||||
Collections.emptyList()));
|
||||
mDelegate.beginTransactionWithListener(transactionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginTransactionWithListenerNonExclusive(
|
||||
@NonNull SQLiteTransactionListener transactionListener) {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery("BEGIN DEFERRED TRANSACTION",
|
||||
Collections.emptyList()));
|
||||
mDelegate.beginTransactionWithListenerNonExclusive(transactionListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endTransaction() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery("END TRANSACTION",
|
||||
Collections.emptyList()));
|
||||
mDelegate.endTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransactionSuccessful() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery("TRANSACTION SUCCESSFUL",
|
||||
Collections.emptyList()));
|
||||
mDelegate.setTransactionSuccessful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inTransaction() {
|
||||
return mDelegate.inTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDbLockedByCurrentThread() {
|
||||
return mDelegate.isDbLockedByCurrentThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean yieldIfContendedSafely() {
|
||||
return mDelegate.yieldIfContendedSafely();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
|
||||
return mDelegate.yieldIfContendedSafely(sleepAfterYieldDelay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return mDelegate.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(int version) {
|
||||
mDelegate.setVersion(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximumSize() {
|
||||
return mDelegate.getMaximumSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long setMaximumSize(long numBytes) {
|
||||
return mDelegate.setMaximumSize(numBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPageSize() {
|
||||
return mDelegate.getPageSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPageSize(long numBytes) {
|
||||
mDelegate.setPageSize(numBytes);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Cursor query(@NonNull String query) {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(query,
|
||||
Collections.emptyList()));
|
||||
return mDelegate.query(query);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Cursor query(@NonNull String query, @NonNull Object[] bindArgs) {
|
||||
List<Object> inputArguments = new ArrayList<>();
|
||||
inputArguments.addAll(Arrays.asList(bindArgs));
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(query,
|
||||
inputArguments));
|
||||
return mDelegate.query(query, bindArgs);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Cursor query(@NonNull SupportSQLiteQuery query) {
|
||||
QueryInterceptorProgram queryInterceptorProgram = new QueryInterceptorProgram();
|
||||
query.bindTo(queryInterceptorProgram);
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(query.getSql(),
|
||||
queryInterceptorProgram.getBindArgs()));
|
||||
return mDelegate.query(query);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Cursor query(@NonNull SupportSQLiteQuery query,
|
||||
@NonNull CancellationSignal cancellationSignal) {
|
||||
QueryInterceptorProgram queryInterceptorProgram = new QueryInterceptorProgram();
|
||||
query.bindTo(queryInterceptorProgram);
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(query.getSql(),
|
||||
queryInterceptorProgram.getBindArgs()));
|
||||
return mDelegate.query(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insert(@NonNull String table, int conflictAlgorithm, @NonNull ContentValues values)
|
||||
throws SQLException {
|
||||
return mDelegate.insert(table, conflictAlgorithm, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull String table, @NonNull String whereClause,
|
||||
@NonNull Object[] whereArgs) {
|
||||
return mDelegate.delete(table, whereClause, whereArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull String table, int conflictAlgorithm, @NonNull ContentValues values,
|
||||
@NonNull String whereClause,
|
||||
@NonNull Object[] whereArgs) {
|
||||
return mDelegate.update(table, conflictAlgorithm, values, whereClause,
|
||||
whereArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execSQL(@NonNull String sql) throws SQLException {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(sql, new ArrayList<>(0)));
|
||||
mDelegate.execSQL(sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execSQL(@NonNull String sql, @NonNull Object[] bindArgs) throws SQLException {
|
||||
List<Object> inputArguments = new ArrayList<>();
|
||||
inputArguments.addAll(Arrays.asList(bindArgs));
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(sql, inputArguments));
|
||||
mDelegate.execSQL(sql, inputArguments.toArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return mDelegate.isReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return mDelegate.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needUpgrade(int newVersion) {
|
||||
return mDelegate.needUpgrade(newVersion);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getPath() {
|
||||
return mDelegate.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocale(@NonNull Locale locale) {
|
||||
mDelegate.setLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxSqlCacheSize(int cacheSize) {
|
||||
mDelegate.setMaxSqlCacheSize(cacheSize);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
@Override
|
||||
public void setForeignKeyConstraintsEnabled(boolean enable) {
|
||||
mDelegate.setForeignKeyConstraintsEnabled(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableWriteAheadLogging() {
|
||||
return mDelegate.enableWriteAheadLogging();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
@Override
|
||||
public void disableWriteAheadLogging() {
|
||||
mDelegate.disableWriteAheadLogging();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
@Override
|
||||
public boolean isWriteAheadLoggingEnabled() {
|
||||
return mDelegate.isWriteAheadLoggingEnabled();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Pair<String, String>> getAttachedDbs() {
|
||||
return mDelegate.getAttachedDbs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDatabaseIntegrityOk() {
|
||||
return mDelegate.isDatabaseIntegrityOk();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mDelegate.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
final class QueryInterceptorOpenHelper implements SupportSQLiteOpenHelper, DelegatingOpenHelper {
|
||||
|
||||
private final SupportSQLiteOpenHelper mDelegate;
|
||||
private final RoomDatabase.QueryCallback mQueryCallback;
|
||||
private final Executor mQueryCallbackExecutor;
|
||||
|
||||
QueryInterceptorOpenHelper(@NonNull SupportSQLiteOpenHelper supportSQLiteOpenHelper,
|
||||
@NonNull RoomDatabase.QueryCallback queryCallback, @NonNull Executor
|
||||
queryCallbackExecutor) {
|
||||
mDelegate = supportSQLiteOpenHelper;
|
||||
mQueryCallback = queryCallback;
|
||||
mQueryCallbackExecutor = queryCallbackExecutor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getDatabaseName() {
|
||||
return mDelegate.getDatabaseName();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
|
||||
@Override
|
||||
public void setWriteAheadLoggingEnabled(boolean enabled) {
|
||||
mDelegate.setWriteAheadLoggingEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportSQLiteDatabase getWritableDatabase() {
|
||||
return new QueryInterceptorDatabase(mDelegate.getWritableDatabase(), mQueryCallback,
|
||||
mQueryCallbackExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportSQLiteDatabase getReadableDatabase() {
|
||||
return new QueryInterceptorDatabase(mDelegate.getReadableDatabase(), mQueryCallback,
|
||||
mQueryCallbackExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mDelegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public SupportSQLiteOpenHelper getDelegate() {
|
||||
return mDelegate;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Implements {@link SupportSQLiteOpenHelper.Factory} to wrap QueryInterceptorOpenHelper.
|
||||
*/
|
||||
@SuppressWarnings("AcronymName")
|
||||
final class QueryInterceptorOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
|
||||
|
||||
private final SupportSQLiteOpenHelper.Factory mDelegate;
|
||||
private final RoomDatabase.QueryCallback mQueryCallback;
|
||||
private final Executor mQueryCallbackExecutor;
|
||||
|
||||
@SuppressWarnings("LambdaLast")
|
||||
QueryInterceptorOpenHelperFactory(@NonNull SupportSQLiteOpenHelper.Factory factory,
|
||||
@NonNull RoomDatabase.QueryCallback queryCallback,
|
||||
@NonNull Executor queryCallbackExecutor) {
|
||||
mDelegate = factory;
|
||||
mQueryCallback = queryCallback;
|
||||
mQueryCallbackExecutor = queryCallbackExecutor;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SupportSQLiteOpenHelper create(
|
||||
@NonNull SupportSQLiteOpenHelper.Configuration configuration) {
|
||||
return new QueryInterceptorOpenHelper(mDelegate.create(configuration), mQueryCallback,
|
||||
mQueryCallbackExecutor);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import androidx.sqlite.db.SupportSQLiteProgram;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A program implementing an {@link SupportSQLiteProgram} API to record bind arguments.
|
||||
*/
|
||||
final class QueryInterceptorProgram implements SupportSQLiteProgram {
|
||||
private List<Object> mBindArgsCache = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void bindNull(int index) {
|
||||
saveArgsToCache(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindLong(int index, long value) {
|
||||
saveArgsToCache(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindDouble(int index, double value) {
|
||||
saveArgsToCache(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindString(int index, String value) {
|
||||
saveArgsToCache(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindBlob(int index, byte[] value) {
|
||||
saveArgsToCache(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBindings() {
|
||||
mBindArgsCache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { }
|
||||
|
||||
private void saveArgsToCache(int bindIndex, Object value) {
|
||||
// The index into bind methods are 1...n
|
||||
int index = bindIndex - 1;
|
||||
if (index >= mBindArgsCache.size()) {
|
||||
for (int i = mBindArgsCache.size(); i <= index; i++) {
|
||||
mBindArgsCache.add(null);
|
||||
}
|
||||
}
|
||||
mBindArgsCache.set(index, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of arguments associated with the query.
|
||||
*
|
||||
* @return argument list.
|
||||
*/
|
||||
List<Object> getBindArgs() {
|
||||
return mBindArgsCache;
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2020 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 androidx.room;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.sqlite.db.SupportSQLiteStatement;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Implements an instance of {@link SupportSQLiteStatement} for SQLite queries.
|
||||
*/
|
||||
final class QueryInterceptorStatement implements SupportSQLiteStatement {
|
||||
|
||||
private final SupportSQLiteStatement mDelegate;
|
||||
private final RoomDatabase.QueryCallback mQueryCallback;
|
||||
private final String mSqlStatement;
|
||||
private final List<Object> mBindArgsCache = new ArrayList<>();
|
||||
private final Executor mQueryCallbackExecutor;
|
||||
|
||||
QueryInterceptorStatement(@NonNull SupportSQLiteStatement compileStatement,
|
||||
@NonNull RoomDatabase.QueryCallback queryCallback, String sqlStatement,
|
||||
@NonNull Executor queryCallbackExecutor) {
|
||||
mDelegate = compileStatement;
|
||||
mQueryCallback = queryCallback;
|
||||
mSqlStatement = sqlStatement;
|
||||
mQueryCallbackExecutor = queryCallbackExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(mSqlStatement, mBindArgsCache));
|
||||
mDelegate.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdateDelete() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(mSqlStatement, mBindArgsCache));
|
||||
return mDelegate.executeUpdateDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long executeInsert() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(mSqlStatement, mBindArgsCache));
|
||||
return mDelegate.executeInsert();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long simpleQueryForLong() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(mSqlStatement, mBindArgsCache));
|
||||
return mDelegate.simpleQueryForLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String simpleQueryForString() {
|
||||
mQueryCallbackExecutor.execute(() -> mQueryCallback.onQuery(mSqlStatement, mBindArgsCache));
|
||||
return mDelegate.simpleQueryForString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index) {
|
||||
saveArgsToCache(index, mBindArgsCache.toArray());
|
||||
mDelegate.bindNull(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindLong(int index, long value) {
|
||||
saveArgsToCache(index, value);
|
||||
mDelegate.bindLong(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindDouble(int index, double value) {
|
||||
saveArgsToCache(index, value);
|
||||
mDelegate.bindDouble(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindString(int index, String value) {
|
||||
saveArgsToCache(index, value);
|
||||
mDelegate.bindString(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindBlob(int index, byte[] value) {
|
||||
saveArgsToCache(index, value);
|
||||
mDelegate.bindBlob(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBindings() {
|
||||
mBindArgsCache.clear();
|
||||
mDelegate.clearBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mDelegate.close();
|
||||
}
|
||||
|
||||
private void saveArgsToCache(int bindIndex, Object value) {
|
||||
int index = bindIndex - 1;
|
||||
if (index >= mBindArgsCache.size()) {
|
||||
// Add null entries to the list until we have the desired # of indices
|
||||
for (int i = mBindArgsCache.size(); i <= index; i++) {
|
||||
mBindArgsCache.add(null);
|
||||
}
|
||||
}
|
||||
mBindArgsCache.set(index, value);
|
||||
}
|
||||
}
|
Loading…
Reference in new issue