19fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik/*
29fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * Copyright (C) 2017 The Android Open Source Project
39fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik *
49fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * Licensed under the Apache License, Version 2.0 (the "License");
59fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * you may not use this file except in compliance with the License.
69fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * You may obtain a copy of the License at
79fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik *
89fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik *      http://www.apache.org/licenses/LICENSE-2.0
99fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik *
109fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * Unless required by applicable law or agreed to in writing, software
119fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * distributed under the License is distributed on an "AS IS" BASIS,
129fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * See the License for the specific language governing permissions and
149fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * limitations under the License.
159fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik */
169fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
179fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikpackage android.arch.persistence.room.paging;
189fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
19abd098954d3fe996f336201ccb25884aaa34e07fYigit Boyarimport android.arch.paging.TiledDataSource;
209fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport android.arch.persistence.room.InvalidationTracker;
219fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport android.arch.persistence.room.RoomDatabase;
229fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport android.arch.persistence.room.RoomSQLiteQuery;
239fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport android.database.Cursor;
249fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport android.support.annotation.NonNull;
259fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport android.support.annotation.Nullable;
269fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport android.support.annotation.RestrictTo;
279fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
289fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport java.util.List;
299fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craikimport java.util.Set;
309fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
319fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik/**
329fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * A simple data source implementation that uses Limit & Offset to page the query.
339fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * <p>
349fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * This is NOT the most efficient way to do paging on SQLite. It is
359fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * <a href="http://www.sqlite.org/cvstrac/wiki?p=ScrollingCursor">recommended</a> to use an indexed
369fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * ORDER BY statement but that requires a more complex API. This solution is technically equal to
379fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * receiving a {@link Cursor} from a large query but avoids the need to manually manage it, and
389fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * never returns inconsistent data if it is invalidated.
399fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik *
409fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * @param <T> Data type returned by the data source.
419fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik *
429fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik * @hide
439fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik */
449fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
45abd098954d3fe996f336201ccb25884aaa34e07fYigit Boyarpublic abstract class LimitOffsetDataSource<T> extends TiledDataSource<T> {
469fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    private final RoomSQLiteQuery mSourceQuery;
479fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    private final String mCountQuery;
489fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    private final String mLimitOffsetQuery;
499fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    private final RoomDatabase mDb;
509fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    @SuppressWarnings("FieldCanBeLocal")
519fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    private final InvalidationTracker.Observer mObserver;
529fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
5324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik    protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, String... tables) {
549fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        mDb = db;
559fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        mSourceQuery = query;
569fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )";
579fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
589fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        mObserver = new InvalidationTracker.Observer(tables) {
599fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            @Override
609fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            public void onInvalidated(@NonNull Set<String> tables) {
619fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik                invalidate();
629fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            }
639fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        };
649fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        db.getInvalidationTracker().addWeakObserver(mObserver);
659fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    }
669fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
679fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    @Override
68f0d13608aae3b4700d84c1c4532abbea56ea7a28Chris Craik    public int countItems() {
699fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mCountQuery,
709fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik                mSourceQuery.getArgCount());
719fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        sqLiteQuery.copyArgumentsFrom(mSourceQuery);
729fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        Cursor cursor = mDb.query(sqLiteQuery);
739fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        try {
749fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            if (cursor.moveToFirst()) {
759fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik                return cursor.getInt(0);
769fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            }
779fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            return 0;
789fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        } finally {
799fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            cursor.close();
809fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            sqLiteQuery.release();
819fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        }
829fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    }
839fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
849fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    @Override
859fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    public boolean isInvalid() {
869fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        mDb.getInvalidationTracker().refreshVersionsSync();
879fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        return super.isInvalid();
889fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    }
899fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik
90fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @SuppressWarnings("WeakerAccess")
91fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    protected abstract List<T> convertRows(Cursor cursor);
92fd4fa4a65be59806d14e4625397948da008506b4Chris Craik
93fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Nullable
94fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    @Override
95fd4fa4a65be59806d14e4625397948da008506b4Chris Craik    public List<T> loadRange(int startPosition, int loadCount) {
969fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mLimitOffsetQuery,
979fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik                mSourceQuery.getArgCount() + 2);
989fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        sqLiteQuery.copyArgumentsFrom(mSourceQuery);
99fd4fa4a65be59806d14e4625397948da008506b4Chris Craik        sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
100fd4fa4a65be59806d14e4625397948da008506b4Chris Craik        sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
1019fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        Cursor cursor = mDb.query(sqLiteQuery);
10224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik
1039fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        try {
1049fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            return convertRows(cursor);
1059fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        } finally {
1069fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            cursor.close();
1079fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik            sqLiteQuery.release();
1089fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik        }
1099fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik    }
1109fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik}
111