Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(firestore): support limitToLast query filter #3702

Merged
merged 7 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,488 changes: 1,300 additions & 1,188 deletions docs/typedoc.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/typedoc.min.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ private void applyOptions(ReadableMap options) {
query = query.limit(limit);
}

if (options.hasKey("limitToLast")) {
int limitToLast = options.getInt("limitToLast");
query = query.limitToLast(limitToLast);
}

if (options.hasKey("startAt")) {
List<Object> fieldList = parseReadableArray(query.getFirestore(), options.getArray("startAt"));
query = query.startAt(Objects.requireNonNull(fieldList.toArray()));
Expand Down
106 changes: 106 additions & 0 deletions packages/firestore/e2e/Query/limitToLast.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2016-present Invertase Limited & Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this library 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.
*
*/

const { wipe } = require('../helpers');

describe('firestore().collection().limitToLast()', () => {
before(() => wipe());

it('throws if limitToLast is invalid', () => {
try {
firebase
.firestore()
.collection('v6')
.limitToLast(-1);
return Promise.reject(new Error('Did not throw an Error.'));
} catch (error) {
error.message.should.containEql("'limitToLast' must be a positive integer value");
return Promise.resolve();
}
});

it('sets limitToLast on internals', async () => {
const colRef = firebase
.firestore()
.collection('v6')
.limitToLast(123);

should(colRef._modifiers.options.limitToLast).equal(123);
});

it('removes limit query if limitToLast is set afterwards', () => {
const colRef = firebase
.firestore()
.collection('v6')
.limit(2)
.limitToLast(123);

should(colRef._modifiers.options.limit).equal(undefined);
});

it('removes limitToLast query if limit is set afterwards', () => {
const colRef = firebase
.firestore()
.collection('v6')
.limitToLast(123)
.limit(2);

should(colRef._modifiers.options.limitToLast).equal(undefined);
});

it('limitToLast the number of documents', async () => {
const colRef = firebase.firestore().collection('v6');

// Add 3
await colRef.add({ count: 1 });
await colRef.add({ count: 2 });
await colRef.add({ count: 3 });

const docs = await firebase
.firestore()
.collection('v6')
.limitToLast(2)
.orderBy('count', 'desc')
.get();

const results = [];
docs.forEach(doc => {
results.push(doc.data());
});

should(results.length).equal(2);

should(results[0].count).equal(2);
should(results[1].count).equal(1);
});

it("throws error if no 'orderBy' is set on the query", () => {
try {
firebase
.firestore()
.collection('v6')
.limitToLast(3)
.get();
return Promise.reject(new Error('Did not throw an Error.'));
} catch (error) {
error.message.should.containEql(
'limitToLast() queries require specifying at least one firebase.firestore().collection().orderBy() clause',
);
return Promise.resolve();
}
});
});
4 changes: 4 additions & 0 deletions packages/firestore/ios/RNFBFirestore/RNFBFirestoreQuery.m
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ - (void)applyOptions {
if (_options[@"limit"]) {
_query = [_query queryLimitedTo:[_options[@"limit"] intValue]];
}

if (_options[@"limitToLast"]) {
_query = [_query queryLimitedToLast:[_options[@"limitToLast"] intValue]];
}

if (_options[@"startAt"]) {
NSArray *fieldList = [RNFBFirestoreSerialize parseNSArray:_firestore array:_options[@"startAt"]];
Expand Down
18 changes: 16 additions & 2 deletions packages/firestore/lib/FirestoreQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ export default class FirestoreQuery {
);
}

this._modifiers.validatelimitToLast();

return this._firestore.native
.collectionGet(
this._collectionPath.relativeName,
Expand Down Expand Up @@ -220,12 +222,26 @@ export default class FirestoreQuery {
return new FirestoreQuery(this._firestore, this._collectionPath, modifiers);
}

limitToLast(limitToLast) {
if (this._modifiers.isValidLimitToLast(limitToLast)) {
throw new Error(
"firebase.firestore().collection().limitToLast(*) 'limitToLast' must be a positive integer value.",
);
}

const modifiers = this._modifiers._copy().limitToLast(limitToLast);

return new FirestoreQuery(this._firestore, this._collectionPath, modifiers);
}

onSnapshot(...args) {
let snapshotListenOptions;
let callback;
let onNext;
let onError;

this._modifiers.validatelimitToLast();

try {
const options = parseSnapshotArgs(args);
snapshotListenOptions = options.snapshotListenOptions;
Expand Down Expand Up @@ -405,8 +421,6 @@ export default class FirestoreQuery {
throw new Error(`firebase.firestore().collection().where() ${e.message}`);
}

modifiers.validateWhere();

return new FirestoreQuery(this._firestore, this._collectionPath, modifiers);
}
}
32 changes: 32 additions & 0 deletions packages/firestore/lib/FirestoreQueryModifiers.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const DIRECTIONS = {
export default class FirestoreQueryModifiers {
constructor() {
this._limit = undefined;
this._limitToLast = undefined;
this._filters = [];
this._orders = [];
this._type = 'collection';
Expand All @@ -58,6 +59,7 @@ export default class FirestoreQueryModifiers {
_copy() {
const newInstance = new FirestoreQueryModifiers();
newInstance._limit = this._limit;
newInstance._limitToLast = this._limitToLast;
newInstance._filters = [...this._filters];
newInstance._orders = [...this._orders];
newInstance._type = this._type;
Expand All @@ -82,6 +84,11 @@ export default class FirestoreQueryModifiers {
if (this._limit) {
options.limit = this._limit;
}

if (this._limitToLast) {
options.limitToLast = this._limitToLast;
}

if (this._startAt) {
options.startAt = this._startAt;
}
Expand Down Expand Up @@ -141,10 +148,35 @@ export default class FirestoreQueryModifiers {
}

limit(limit) {
this._limitToLast = undefined;
this._limit = limit;
return this;
}

/**
* limitToLast
*/

isValidLimitToLast(limit) {
return !isNumber(limit) || Math.floor(limit) !== limit || limit <= 0;
}

validatelimitToLast() {
if (this._limitToLast) {
if (!this._orders.length) {
throw new Error(
'firebase.firestore().collection().limitToLast() queries require specifying at least one firebase.firestore().collection().orderBy() clause',
);
}
}
}

limitToLast(limitToLast) {
this._limit = undefined;
this._limitToLast = limitToLast;
return this;
}

/**
* Filters
*/
Expand Down
19 changes: 19 additions & 0 deletions packages/firestore/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,25 @@ export namespace FirebaseFirestoreTypes {
* @param limit The maximum number of items to return.
*/
limit(limit: number): Query;
/**
* Creates and returns a new Query where the results are limited to the specified number of documents
* starting from the last document. The order is dependent on the second parameter for the `orderBy`
* method. If `desc` is used, the order is reversed. `orderBy` method call is required when calling `limitToLast`.
*
* #### Example
*
* ```js
* // Get the last 10 users in reverse order of age
* const querySnapshot = firebase.firestore()
* .collection('users')
* .orderBy('age', 'desc')
* .limitToLast(10)
* .get();
* ```
*
* @param limitToLast The maximum number of items to return.
*/
limitToLast(limitToLast: number): Query;

/**
* Attaches a listener for `QuerySnapshot` events.
Expand Down