LimitOffsetDataSource.java revision fd4fa4a65be59806d14e4625397948da008506b4
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 19ef346ae131affbba6345e00d833103acc5743c8aChris Craikimport android.arch.paging.BoundedDataSource; 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) 4524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craikpublic abstract class LimitOffsetDataSource<T> extends BoundedDataSource<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 689fd8e6171bbdc37f5516fe15b2d96f4ae926ef1aChris Craik public int loadCount() { 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