1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.room.integration.testapp.test;
18 
19 import static org.hamcrest.CoreMatchers.is;
20 import static org.hamcrest.CoreMatchers.notNullValue;
21 import static org.hamcrest.MatcherAssert.assertThat;
22 
23 import android.content.ContentValues;
24 import android.database.Cursor;
25 import android.database.SQLException;
26 import android.database.sqlite.SQLiteTransactionListener;
27 import android.os.CancellationSignal;
28 import android.util.Pair;
29 
30 import androidx.arch.core.executor.ArchTaskExecutor;
31 import androidx.arch.core.executor.testing.CountingTaskExecutorRule;
32 import androidx.lifecycle.Lifecycle;
33 import androidx.lifecycle.LiveData;
34 import androidx.lifecycle.testing.TestLifecycleOwner;
35 import androidx.paging.DataSource;
36 import androidx.paging.LivePagedListBuilder;
37 import androidx.paging.PagedList;
38 import androidx.paging.PositionalDataSource;
39 import androidx.room.Dao;
40 import androidx.room.Database;
41 import androidx.room.Entity;
42 import androidx.room.Ignore;
43 import androidx.room.Insert;
44 import androidx.room.PrimaryKey;
45 import androidx.room.Query;
46 import androidx.room.Relation;
47 import androidx.room.Room;
48 import androidx.room.RoomDatabase;
49 import androidx.room.RoomWarnings;
50 import androidx.room.Transaction;
51 import androidx.room.paging.LimitOffsetDataSource;
52 import androidx.sqlite.db.SupportSQLiteDatabase;
53 import androidx.sqlite.db.SupportSQLiteOpenHelper;
54 import androidx.sqlite.db.SupportSQLiteQuery;
55 import androidx.sqlite.db.SupportSQLiteStatement;
56 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
57 import androidx.test.core.app.ApplicationProvider;
58 import androidx.test.filters.SmallTest;
59 
60 import io.reactivex.Flowable;
61 import io.reactivex.Maybe;
62 import io.reactivex.Single;
63 import io.reactivex.observers.TestObserver;
64 import io.reactivex.schedulers.Schedulers;
65 import io.reactivex.subscribers.TestSubscriber;
66 
67 import org.jspecify.annotations.NonNull;
68 import org.jspecify.annotations.Nullable;
69 import org.junit.After;
70 import org.junit.Before;
71 import org.junit.Rule;
72 import org.junit.Test;
73 import org.junit.runner.RunWith;
74 import org.junit.runners.Parameterized;
75 
76 import java.io.IOException;
77 import java.util.Collections;
78 import java.util.List;
79 import java.util.Locale;
80 import java.util.concurrent.ExecutionException;
81 import java.util.concurrent.FutureTask;
82 import java.util.concurrent.TimeUnit;
83 import java.util.concurrent.TimeoutException;
84 import java.util.concurrent.atomic.AtomicInteger;
85 
86 @SmallTest
87 @RunWith(Parameterized.class)
88 @SuppressWarnings("CheckReturnValue")
89 public class QueryTransactionTest {
90     @Rule
91     public CountingTaskExecutorRule countingTaskExecutorRule = new CountingTaskExecutorRule();
92     private static final AtomicInteger sStartedTransactionCount = new AtomicInteger(0);
93     private TransactionDb mDb;
94     private final boolean mUseTransactionDao;
95     private Entity1Dao mDao;
96     private final TestLifecycleOwner mLifecycleOwner = new TestLifecycleOwner();
97 
98     @Parameterized.Parameters(name = "useTransaction_{0}")
getParams()99     public static Boolean @NonNull [] getParams() {
100         return new Boolean[]{false, true};
101     }
102 
QueryTransactionTest(boolean useTransactionDao)103     public QueryTransactionTest(boolean useTransactionDao) {
104         mUseTransactionDao = useTransactionDao;
105     }
106 
107     @Before
initDb()108     public void initDb() {
109         resetTransactionCount();
110         mDb = Room.inMemoryDatabaseBuilder(
111                 ApplicationProvider.getApplicationContext(),
112                 TransactionDb.class)
113                 .openHelperFactory(new TransactionOpenHelperFactory())
114                 .build();
115         mDao = mUseTransactionDao ? mDb.transactionDao() : mDb.dao();
116         drain();
117     }
118 
119     @After
closeDb()120     public void closeDb() {
121         mLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
122         drain();
123         mDb.close();
124     }
125 
126     @Test
readList()127     public void readList() {
128         mDao.insert(new Entity1(1, "foo"));
129         resetTransactionCount();
130 
131         int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
132         List<Entity1> allEntities = mDao.allEntities();
133         assertTransactionCount(allEntities, expectedTransactionCount);
134     }
135 
136     @Test
liveData()137     public void liveData() {
138         LiveData<List<Entity1>> listLiveData = mDao.liveData();
139         observeForever(listLiveData);
140         drain();
141         assertThat(listLiveData.getValue(), is(Collections.<Entity1>emptyList()));
142 
143         resetTransactionCount();
144         mDao.insert(new Entity1(1, "foo"));
145         drain();
146 
147         //noinspection ConstantConditions
148         assertThat(listLiveData.getValue().size(), is(1));
149         int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
150         assertTransactionCount(listLiveData.getValue(), expectedTransactionCount);
151     }
152 
153     @Test
flowable()154     public void flowable() {
155         Flowable<List<Entity1>> flowable = mDao.flowable();
156         TestSubscriber<List<Entity1>> subscriber = observe(flowable);
157         drain();
158         assertThat(subscriber.values().size(), is(1));
159 
160         resetTransactionCount();
161         mDao.insert(new Entity1(1, "foo"));
162         drain();
163 
164         List<Entity1> allEntities = subscriber.values().get(1);
165         assertThat(allEntities.size(), is(1));
166         int expectedTransactionCount = mUseTransactionDao ? 2 : 1;
167         assertTransactionCount(allEntities, expectedTransactionCount);
168     }
169 
170     @Test
maybe()171     public void maybe() {
172         mDao.insert(new Entity1(1, "foo"));
173         resetTransactionCount();
174 
175         int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
176         Maybe<List<Entity1>> listMaybe = mDao.maybe();
177         TestObserver<List<Entity1>> observer = observe(listMaybe);
178         drain();
179         List<Entity1> allEntities = observer.values().get(0);
180         assertTransactionCount(allEntities, expectedTransactionCount);
181     }
182 
183     @Test
single()184     public void single() {
185         mDao.insert(new Entity1(1, "foo"));
186         resetTransactionCount();
187 
188         int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
189         Single<List<Entity1>> listMaybe = mDao.single();
190         TestObserver<List<Entity1>> observer = observe(listMaybe);
191         drain();
192         List<Entity1> allEntities = observer.values().get(0);
193         assertTransactionCount(allEntities, expectedTransactionCount);
194     }
195 
196     @Test
relation()197     public void relation() {
198         mDao.insert(new Entity1(1, "foo"));
199         mDao.insert(new Child(1, 1));
200         mDao.insert(new Child(2, 1));
201         resetTransactionCount();
202 
203         List<Entity1WithChildren> result = mDao.withRelation();
204         int expectedTransactionCount = mUseTransactionDao ? 1 : 0;
205         assertTransactionCountWithChildren(result, expectedTransactionCount);
206     }
207 
208     @Test
pagedList()209     public void pagedList() {
210         PagedList.Config config = new PagedList.Config.Builder()
211                 .setPageSize(1)
212                 .setInitialLoadSizeHint(3)
213                 .setPrefetchDistance(2)
214                 .setEnablePlaceholders(false)
215                 .build();
216         LiveData<PagedList<Entity1>> pagedList =
217                 new LivePagedListBuilder<>(mDao.pagedList(), config).build();
218         observeForever(pagedList);
219         drain();
220         assertThat(sStartedTransactionCount.get(), is(1));
221 
222         mDao.insert(new Entity1(1, "foo"));
223         drain();
224         //noinspection ConstantConditions
225         assertThat(pagedList.getValue().size(), is(1));
226         assertTransactionCount(pagedList.getValue(), 3);
227 
228         mDao.insert(
229                 new Entity1(2, "bar"),
230                 new Entity1(3, "baz"),
231                 new Entity1(4, "bup"));
232         drain();
233         assertThat(pagedList.getValue().size(), is(3));
234         assertTransactionCount(pagedList.getValue(), 5);
235 
236         // only loadRange is affected by transaction dao, since initial load requires transaction
237         pagedList.getValue().loadAround(2);
238         drain();
239         assertThat(pagedList.getValue().size(), is(4));
240         // note: we don't use assertTransactionCount here, since last item loaded separately
241         assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 7 : 5));
242     }
243 
244     @Test
dataSourceRange()245     public void dataSourceRange() {
246         mDao.insert(new Entity1(2, "bar"));
247         drain();
248         resetTransactionCount();
249         LimitOffsetDataSource<Entity1> dataSource =
250                 (LimitOffsetDataSource<Entity1>) mDao.dataSource();
251         dataSource.loadRange(0, 10);
252         assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 1 : 0));
253     }
254 
255     @SuppressWarnings("deprecation")
256     @Test
dataSourceInitial()257     public void dataSourceInitial() {
258         mDao.insert(new Entity1(2, "bar"));
259         drain();
260         resetTransactionCount();
261         LimitOffsetDataSource<Entity1> dataSource =
262                 (LimitOffsetDataSource<Entity1>) mDao.dataSource();
263         dataSource.loadInitial(
264                 new PositionalDataSource.LoadInitialParams(0, 30, 10, true),
265                 new PositionalDataSource.LoadInitialCallback<Entity1>() {
266                     @Override
267                     public void onResult(@NonNull List<? extends Entity1> data, int position,
268                             int totalCount) {}
269 
270                     @Override
271                     public void onResult(@NonNull List<? extends Entity1> data, int position) {}
272                 });
273         // always use a transaction, since we're loading count + initial data
274         assertThat(sStartedTransactionCount.get(), is(1));
275     }
276 
assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount)277     private void assertTransactionCount(List<Entity1> allEntities, int expectedTransactionCount) {
278         assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
279         assertThat(allEntities.isEmpty(), is(false));
280         for (Entity1 entity1 : allEntities) {
281             assertThat(entity1.transactionId, is(expectedTransactionCount));
282         }
283     }
284 
assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities, int expectedTransactionCount)285     private void assertTransactionCountWithChildren(List<Entity1WithChildren> allEntities,
286             int expectedTransactionCount) {
287         assertThat(sStartedTransactionCount.get(), is(expectedTransactionCount));
288         assertThat(allEntities.isEmpty(), is(false));
289         for (Entity1WithChildren entity1 : allEntities) {
290             assertThat(entity1.transactionId, is(expectedTransactionCount));
291             assertThat(entity1.children, notNullValue());
292             assertThat(entity1.children.isEmpty(), is(false));
293             for (Child child : entity1.children) {
294                 assertThat(child.transactionId, is(expectedTransactionCount));
295             }
296         }
297     }
298 
incrementTransactionCount()299     private static void incrementTransactionCount() {
300         // When incrementing the transaction count, ignore those coming from the refresh
301         // in the invalidation tracker.
302         StackTraceElement[] stack = Thread.currentThread().getStackTrace();
303         for (StackTraceElement element : stack) {
304             String fileName = element.getFileName();
305             if (fileName != null && fileName.equals("InvalidationTracker.kt")) {
306                 return;
307             }
308         }
309         sStartedTransactionCount.incrementAndGet();
310     }
311 
resetTransactionCount()312     private void resetTransactionCount() {
313         sStartedTransactionCount.set(0);
314     }
315 
drain()316     private void drain() {
317         try {
318             countingTaskExecutorRule.drainTasks(3, TimeUnit.SECONDS);
319         } catch (InterruptedException e) {
320             throw new AssertionError("interrupted", e);
321         } catch (TimeoutException e) {
322             throw new AssertionError("drain timed out", e);
323         }
324     }
325 
observe(final Flowable<T> flowable)326     private <T> TestSubscriber<T> observe(final Flowable<T> flowable) {
327         TestSubscriber<T> subscriber = new TestSubscriber<>();
328         flowable.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
329                 .subscribeWith(subscriber);
330         return subscriber;
331     }
332 
observe(final Maybe<T> maybe)333     private <T> TestObserver<T> observe(final Maybe<T> maybe) {
334         TestObserver<T> observer = new TestObserver<>();
335         maybe.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
336                 .subscribeWith(observer);
337         return observer;
338     }
339 
observe(final Single<T> single)340     private <T> TestObserver<T> observe(final Single<T> single) {
341         TestObserver<T> observer = new TestObserver<>();
342         single.observeOn(Schedulers.from(ArchTaskExecutor.getMainThreadExecutor()))
343                 .subscribeWith(observer);
344         return observer;
345     }
346 
observeForever(final LiveData<T> liveData)347     private <T> void observeForever(final LiveData<T> liveData) {
348         FutureTask<Void> futureTask = new FutureTask<>(() -> {
349             liveData.observe(mLifecycleOwner, t -> {
350             });
351             return null;
352         });
353         ArchTaskExecutor.getMainThreadExecutor().execute(futureTask);
354         try {
355             futureTask.get();
356         } catch (InterruptedException e) {
357             throw new AssertionError("interrupted", e);
358         } catch (ExecutionException e) {
359             throw new AssertionError("execution error", e);
360         }
361     }
362 
363     @SuppressWarnings("WeakerAccess")
364     static class Entity1WithChildren extends Entity1 {
365         @Relation(entity = Child.class, parentColumn = "id",
366                 entityColumn = "entity1Id")
367         public List<Child> children;
368 
Entity1WithChildren(int id, String value)369         Entity1WithChildren(int id, String value) {
370             super(id, value);
371         }
372     }
373 
374     @SuppressWarnings("WeakerAccess")
375     @Entity
376     static class Child {
377         @PrimaryKey(autoGenerate = true)
378         public int id;
379         public int entity1Id;
380         @Ignore
381         public final int transactionId = sStartedTransactionCount.get();
382 
Child(int id, int entity1Id)383         Child(int id, int entity1Id) {
384             this.id = id;
385             this.entity1Id = entity1Id;
386         }
387     }
388 
389     @SuppressWarnings("WeakerAccess")
390     @Entity
391     static class Entity1 {
392         @PrimaryKey(autoGenerate = true)
393         public int id;
394         public String value;
395         @Ignore
396         public final int transactionId = sStartedTransactionCount.get();
397 
Entity1(int id, String value)398         Entity1(int id, String value) {
399             this.id = id;
400             this.value = value;
401         }
402     }
403 
404     // we don't support dao inheritance for queries so for now, go with this
405     interface Entity1Dao {
406         String SELECT_ALL = "select * from Entity1";
407 
allEntities()408         List<Entity1> allEntities();
409 
flowable()410         Flowable<List<Entity1>> flowable();
411 
maybe()412         Maybe<List<Entity1>> maybe();
413 
single()414         Single<List<Entity1>> single();
415 
liveData()416         LiveData<List<Entity1>> liveData();
417 
withRelation()418         List<Entity1WithChildren> withRelation();
419 
pagedList()420         DataSource.Factory<Integer, Entity1> pagedList();
421 
dataSource()422         PositionalDataSource<Entity1> dataSource();
423 
424         @Insert
insert(Entity1 entity1)425         void insert(Entity1 entity1);
426 
427         @Insert
insert(Entity1... entities)428         void insert(Entity1... entities);
429 
430         @Insert
insert(Child entity1)431         void insert(Child entity1);
432     }
433 
434     @Dao
435     interface EntityDao extends Entity1Dao {
436         @Override
437         @Query(SELECT_ALL)
allEntities()438         List<Entity1> allEntities();
439 
440         @Override
441         @Query(SELECT_ALL)
flowable()442         Flowable<List<Entity1>> flowable();
443 
444         @Override
445         @Query(SELECT_ALL)
liveData()446         LiveData<List<Entity1>> liveData();
447 
448         @Override
449         @Query(SELECT_ALL)
maybe()450         Maybe<List<Entity1>> maybe();
451 
452         @Override
453         @Query(SELECT_ALL)
single()454         Single<List<Entity1>> single();
455 
456         @Override
457         @Query(SELECT_ALL)
458         @SuppressWarnings(RoomWarnings.RELATION_QUERY_WITHOUT_TRANSACTION)
withRelation()459         List<Entity1WithChildren> withRelation();
460 
461         @Override
462         @Query(SELECT_ALL)
pagedList()463         DataSource.Factory<Integer, Entity1> pagedList();
464 
465         @Override
466         @Query(SELECT_ALL)
dataSource()467         PositionalDataSource<Entity1> dataSource();
468     }
469 
470     @Dao
471     interface TransactionDao extends Entity1Dao {
472         @Override
473         @Transaction
474         @Query(SELECT_ALL)
allEntities()475         List<Entity1> allEntities();
476 
477         @Override
478         @Transaction
479         @Query(SELECT_ALL)
flowable()480         Flowable<List<Entity1>> flowable();
481 
482         @Override
483         @Transaction
484         @Query(SELECT_ALL)
liveData()485         LiveData<List<Entity1>> liveData();
486 
487         @Override
488         @Transaction
489         @Query(SELECT_ALL)
maybe()490         Maybe<List<Entity1>> maybe();
491 
492         @Override
493         @Transaction
494         @Query(SELECT_ALL)
single()495         Single<List<Entity1>> single();
496 
497         @Override
498         @Transaction
499         @Query(SELECT_ALL)
withRelation()500         List<Entity1WithChildren> withRelation();
501 
502         @Override
503         @Transaction
504         @Query(SELECT_ALL)
pagedList()505         DataSource.Factory<Integer, Entity1> pagedList();
506 
507         @Override
508         @Transaction
509         @Query(SELECT_ALL)
dataSource()510         PositionalDataSource<Entity1> dataSource();
511     }
512 
513     @Database(version = 1, entities = {Entity1.class, Child.class}, exportSchema = false)
514     @SuppressWarnings("deprecation")
515     abstract static class TransactionDb extends RoomDatabase {
dao()516         abstract EntityDao dao();
517 
transactionDao()518         abstract TransactionDao transactionDao();
519     }
520 
521     static class TransactionOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
522 
523         private final SupportSQLiteOpenHelper.Factory mDelegate =
524                 new FrameworkSQLiteOpenHelperFactory();
525 
526         @Override
create( SupportSQLiteOpenHelper.@onNull Configuration configuration)527         public @NonNull SupportSQLiteOpenHelper create(
528                 SupportSQLiteOpenHelper.@NonNull Configuration configuration) {
529             return new TransactionSupportSQLiteOpenHelper(mDelegate.create(configuration));
530         }
531     }
532 
533     static class TransactionSupportSQLiteOpenHelper implements SupportSQLiteOpenHelper {
534         private final SupportSQLiteOpenHelper mDelegate;
535 
TransactionSupportSQLiteOpenHelper(SupportSQLiteOpenHelper delegate)536         TransactionSupportSQLiteOpenHelper(SupportSQLiteOpenHelper delegate) {
537             this.mDelegate = delegate;
538         }
539 
540         @Override
getDatabaseName()541         public @Nullable String getDatabaseName() {
542             return mDelegate.getDatabaseName();
543         }
544 
545         @Override
setWriteAheadLoggingEnabled(boolean enabled)546         public void setWriteAheadLoggingEnabled(boolean enabled) {
547             mDelegate.setWriteAheadLoggingEnabled(enabled);
548         }
549 
550         @Override
getWritableDatabase()551         public @NonNull SupportSQLiteDatabase getWritableDatabase() {
552             return new TransactionSupportSQLiteDatabase(mDelegate.getWritableDatabase());
553         }
554 
555         @Override
getReadableDatabase()556         public @NonNull SupportSQLiteDatabase getReadableDatabase() {
557             return new TransactionSupportSQLiteDatabase(mDelegate.getReadableDatabase());
558         }
559 
560         @Override
close()561         public void close() {
562             mDelegate.close();
563         }
564     }
565 
566     static class TransactionSupportSQLiteDatabase implements SupportSQLiteDatabase {
567         private final SupportSQLiteDatabase mDelegate;
568 
TransactionSupportSQLiteDatabase(SupportSQLiteDatabase delegate)569         TransactionSupportSQLiteDatabase(SupportSQLiteDatabase delegate) {
570             this.mDelegate = delegate;
571         }
572 
573         @Override
compileStatement(@onNull String sql)574         public @NonNull SupportSQLiteStatement compileStatement(@NonNull String sql) {
575             return mDelegate.compileStatement(sql);
576         }
577 
578         @Override
beginTransaction()579         public void beginTransaction() {
580             mDelegate.beginTransaction();
581             incrementTransactionCount();
582         }
583 
584         @Override
beginTransactionNonExclusive()585         public void beginTransactionNonExclusive() {
586             mDelegate.beginTransactionNonExclusive();
587             incrementTransactionCount();
588         }
589 
590         @Override
beginTransactionReadOnly()591         public void beginTransactionReadOnly() {
592             mDelegate.beginTransactionReadOnly();
593             incrementTransactionCount();
594         }
595 
596         @Override
beginTransactionWithListener( @onNull SQLiteTransactionListener transactionListener)597         public void beginTransactionWithListener(
598                 @NonNull SQLiteTransactionListener transactionListener) {
599             mDelegate.beginTransactionWithListener(transactionListener);
600             incrementTransactionCount();
601         }
602 
603         @Override
beginTransactionWithListenerNonExclusive( @onNull SQLiteTransactionListener transactionListener)604         public void beginTransactionWithListenerNonExclusive(
605                 @NonNull SQLiteTransactionListener transactionListener) {
606             mDelegate.beginTransactionWithListenerNonExclusive(transactionListener);
607             incrementTransactionCount();
608         }
609 
610         @Override
beginTransactionWithListenerReadOnly( @onNull SQLiteTransactionListener transactionListener)611         public void beginTransactionWithListenerReadOnly(
612                 @NonNull SQLiteTransactionListener transactionListener) {
613             mDelegate.beginTransactionWithListenerReadOnly(transactionListener);
614             incrementTransactionCount();
615         }
616 
617         @Override
endTransaction()618         public void endTransaction() {
619             mDelegate.endTransaction();
620         }
621 
622         @Override
setTransactionSuccessful()623         public void setTransactionSuccessful() {
624             mDelegate.setTransactionSuccessful();
625         }
626 
627         @Override
inTransaction()628         public boolean inTransaction() {
629             return mDelegate.inTransaction();
630         }
631 
632         @Override
isDbLockedByCurrentThread()633         public boolean isDbLockedByCurrentThread() {
634             return mDelegate.isDbLockedByCurrentThread();
635         }
636 
637         @Override
yieldIfContendedSafely()638         public boolean yieldIfContendedSafely() {
639             return mDelegate.yieldIfContendedSafely();
640         }
641 
642         @Override
yieldIfContendedSafely(long sleepAfterYieldDelayMillis)643         public boolean yieldIfContendedSafely(long sleepAfterYieldDelayMillis) {
644             return mDelegate.yieldIfContendedSafely(sleepAfterYieldDelayMillis);
645         }
646 
647         @Override
isExecPerConnectionSQLSupported()648         public boolean isExecPerConnectionSQLSupported() {
649             return mDelegate.isExecPerConnectionSQLSupported();
650         }
651 
652         @Override
execPerConnectionSQL(@onNull String sql, Object @Nullable [] bindArgs)653         public void execPerConnectionSQL(@NonNull String sql, Object @Nullable [] bindArgs) {
654             mDelegate.execPerConnectionSQL(sql, bindArgs);
655         }
656 
657         @Override
getVersion()658         public int getVersion() {
659             return mDelegate.getVersion();
660         }
661 
662         @Override
setVersion(int i)663         public void setVersion(int i) {
664             mDelegate.setVersion(i);
665         }
666 
667         @Override
getMaximumSize()668         public long getMaximumSize() {
669             return mDelegate.getMaximumSize();
670         }
671 
672         @Override
setMaximumSize(long numBytes)673         public long setMaximumSize(long numBytes) {
674             return mDelegate.setMaximumSize(numBytes);
675         }
676 
677         @Override
getPageSize()678         public long getPageSize() {
679             return mDelegate.getPageSize();
680         }
681 
682         @Override
setPageSize(long l)683         public void setPageSize(long l) {
684             mDelegate.setPageSize(l);
685         }
686 
687         @Override
query(@onNull String query)688         public @NonNull Cursor query(@NonNull String query) {
689             return mDelegate.query(query);
690         }
691 
692         @Override
query(@onNull String query, Object @NonNull [] bindArgs)693         public @NonNull Cursor query(@NonNull String query, Object @NonNull [] bindArgs) {
694             return mDelegate.query(query, bindArgs);
695         }
696 
697         @Override
query(@onNull SupportSQLiteQuery query)698         public @NonNull Cursor query(@NonNull SupportSQLiteQuery query) {
699             return mDelegate.query(query);
700         }
701 
702         @Override
query(@onNull SupportSQLiteQuery query, @Nullable CancellationSignal cancellationSignal)703         public @NonNull Cursor query(@NonNull SupportSQLiteQuery query,
704                 @Nullable CancellationSignal cancellationSignal) {
705             return mDelegate.query(query, cancellationSignal);
706         }
707 
708         @Override
insert(@onNull String table, int conflictAlgorithm, @NonNull ContentValues values)709         public long insert(@NonNull String table, int conflictAlgorithm,
710                 @NonNull ContentValues values) throws SQLException {
711             return mDelegate.insert(table, conflictAlgorithm, values);
712         }
713 
714         @Override
delete(@onNull String table, @Nullable String whereClause, Object @Nullable [] whereArgs)715         public int delete(@NonNull String table, @Nullable String whereClause,
716                 Object @Nullable [] whereArgs) {
717             return mDelegate.delete(table, whereClause, whereArgs);
718         }
719 
720         @Override
update(@onNull String table, int conflictAlgorithm, @NonNull ContentValues values, @Nullable String whereClause, Object @Nullable [] whereArgs)721         public int update(@NonNull String table, int conflictAlgorithm,
722                 @NonNull ContentValues values, @Nullable String whereClause,
723                 Object @Nullable [] whereArgs) {
724             return mDelegate.update(table, conflictAlgorithm, values, whereClause, whereArgs);
725         }
726 
727         @Override
execSQL(@onNull String sql)728         public void execSQL(@NonNull String sql) throws SQLException {
729             mDelegate.execSQL(sql);
730         }
731 
732         @Override
execSQL(@onNull String sql, Object @NonNull [] bindArgs)733         public void execSQL(@NonNull String sql, Object @NonNull [] bindArgs) throws SQLException {
734             mDelegate.execSQL(sql, bindArgs);
735         }
736 
737         @Override
isReadOnly()738         public boolean isReadOnly() {
739             return mDelegate.isReadOnly();
740         }
741 
742         @Override
isOpen()743         public boolean isOpen() {
744             return mDelegate.isOpen();
745         }
746 
747         @Override
needUpgrade(int newVersion)748         public boolean needUpgrade(int newVersion) {
749             return mDelegate.needUpgrade(newVersion);
750         }
751 
752         @Override
getPath()753         public @Nullable String getPath() {
754             return mDelegate.getPath();
755         }
756 
757         @Override
setLocale(@onNull Locale locale)758         public void setLocale(@NonNull Locale locale) {
759             mDelegate.setLocale(locale);
760         }
761 
762         @Override
setMaxSqlCacheSize(int cacheSize)763         public void setMaxSqlCacheSize(int cacheSize) {
764             mDelegate.setMaxSqlCacheSize(cacheSize);
765         }
766 
767         @Override
setForeignKeyConstraintsEnabled(boolean enabled)768         public void setForeignKeyConstraintsEnabled(boolean enabled) {
769             mDelegate.setForeignKeyConstraintsEnabled(enabled);
770         }
771 
772         @Override
enableWriteAheadLogging()773         public boolean enableWriteAheadLogging() {
774             return mDelegate.enableWriteAheadLogging();
775         }
776 
777         @Override
disableWriteAheadLogging()778         public void disableWriteAheadLogging() {
779             mDelegate.disableWriteAheadLogging();
780         }
781 
782         @Override
isWriteAheadLoggingEnabled()783         public boolean isWriteAheadLoggingEnabled() {
784             return mDelegate.isWriteAheadLoggingEnabled();
785         }
786 
787         @Override
getAttachedDbs()788         public @Nullable List<Pair<String, String>> getAttachedDbs() {
789             return mDelegate.getAttachedDbs();
790         }
791 
792         @Override
isDatabaseIntegrityOk()793         public boolean isDatabaseIntegrityOk() {
794             return mDelegate.isDatabaseIntegrityOk();
795         }
796 
797         @Override
close()798         public void close() throws IOException {
799             mDelegate.close();
800         }
801     }
802 }
803