[firestore] Correctly support dates, GeoPoints and other types in `where` clause

This commit is contained in:
Chris Bianca 2017-10-31 15:32:08 +00:00
parent d8fd09adef
commit 6ae0049338
8 changed files with 247 additions and 21 deletions

View File

@ -246,7 +246,7 @@ public class FirestoreSerialize {
return list;
}
private static Object parseTypeMap(FirebaseFirestore firestore, ReadableMap typeMap) {
public static Object parseTypeMap(FirebaseFirestore firestore, ReadableMap typeMap) {
String type = typeMap.getString("type");
if ("boolean".equals(type)) {
return typeMap.getBoolean("value");

View File

@ -14,6 +14,7 @@ import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.DocumentListenOptions;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.Query;
@ -115,22 +116,22 @@ public class RNFirebaseFirestoreCollectionReference {
}
private Query buildQuery() {
Query query = RNFirebaseFirestore.getFirestoreForApp(appName).collection(path);
query = applyFilters(query);
FirebaseFirestore firestore = RNFirebaseFirestore.getFirestoreForApp(appName);
Query query = firestore.collection(path);
query = applyFilters(firestore, query);
query = applyOrders(query);
query = applyOptions(query);
return query;
}
private Query applyFilters(Query query) {
List<Object> filtersList = Utils.recursivelyDeconstructReadableArray(filters);
for (Object f : filtersList) {
Map<String, Object> filter = (Map) f;
String fieldPath = (String) filter.get("fieldPath");
String operator = (String) filter.get("operator");
Object value = filter.get("value");
private Query applyFilters(FirebaseFirestore firestore, Query query) {
for (int i = 0; i < filters.size(); i++) {
ReadableMap filter = filters.getMap(i);
String fieldPath = filter.getString("fieldPath");
String operator = filter.getString("operator");
ReadableMap jsValue = filter.getMap("value");
Object value = FirestoreSerialize.parseTypeMap(firestore, jsValue);
switch (operator) {
case "EQUAL":

View File

@ -81,20 +81,22 @@ queryListenOptions:(NSDictionary *) queryListenOptions {
}
- (FIRQuery *)buildQuery {
FIRQuery *query = (FIRQuery*)[[RNFirebaseFirestore getFirestoreForApp:_app] collectionWithPath:_path];
query = [self applyFilters:query];
FIRFirestore *firestore = [RNFirebaseFirestore getFirestoreForApp:_app];
FIRQuery *query = (FIRQuery*)[firestore collectionWithPath:_path];
query = [self applyFilters:firestore query:query];
query = [self applyOrders:query];
query = [self applyOptions:query];
return query;
}
- (FIRQuery *)applyFilters:(FIRQuery *) query {
- (FIRQuery *)applyFilters:(FIRFirestore *) firestore
query:(FIRQuery *) query {
for (NSDictionary *filter in _filters) {
NSString *fieldPath = filter[@"fieldPath"];
NSString *operator = filter[@"operator"];
// TODO: Validate this works
id value = filter[@"value"];
NSDictionary *jsValue = filter[@"value"];
id value = [RNFirebaseFirestoreDocumentReference parseJSTypeMap:firestore jsTypeMap:jsValue];
if ([operator isEqualToString:@"EQUAL"]) {
query = [query queryWhereField:fieldPath isEqualTo:value];

View File

@ -27,6 +27,7 @@
- (BOOL)hasListeners;
+ (NSDictionary *)snapshotToDictionary:(FIRDocumentSnapshot *)documentSnapshot;
+(NSDictionary *)parseJSMap:(FIRFirestore *) firestore jsMap:(NSDictionary *) jsMap;
+(id)parseJSTypeMap:(FIRFirestore *) firestore jsTypeMap:(NSDictionary *) jsTypeMap;
@end
#else

View File

@ -5,6 +5,7 @@
import DocumentSnapshot from './DocumentSnapshot';
import Path from './Path';
import QuerySnapshot from './QuerySnapshot';
import { buildTypeMap } from './utils/serialize';
import { firestoreAutoId, isFunction, isObject } from '../../utils';
const DIRECTIONS = {
@ -261,10 +262,11 @@ export default class Query {
// TODO: Validation
// validate.isFieldPath('fieldPath', fieldPath);
// validate.isFieldFilter('fieldFilter', opStr, value);
const nativeValue = buildTypeMap(value);
const newFilter = {
fieldPath,
operator: OPERATORS[opStr],
value,
value: nativeValue,
};
const combinedFilters = this._fieldFilters.concat(newFilter);
return new Query(this.firestore, this._referencePath, combinedFilters,

View File

@ -37,7 +37,7 @@ const buildNativeArray = (array: Object[]): any[] => {
return nativeArray;
};
const buildTypeMap = (value: any): any => {
export const buildTypeMap = (value: any): any => {
const typeMap = {};
const type = typeOf(value);
if (value === null || value === undefined) {

View File

@ -2,9 +2,9 @@ import sinon from 'sinon';
import 'should-sinon';
import should from 'should';
import { COL_1 } from './index';
import { COL_1, cleanCollection } from './index';
function collectionReferenceTests({ describe, it, context, firebase }) {
function collectionReferenceTests({ describe, it, context, firebase, before, after }) {
describe('CollectionReference', () => {
context('class', () => {
it('should return instance methods', () => {
@ -445,6 +445,26 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
it('correctly handles == date values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('timestamp', '==', COL_1.timestamp)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
});
});
it('correctly handles == geopoint values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('geopoint', '==', COL_1.geopoint)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
});
});
it('correctly handles >= number values', () => {
return firebase.native.firestore()
.collection('collection-tests')
@ -458,6 +478,16 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
it('correctly handles >= geopoint values', () => {
return firebase.native.firestore()
.collection('collection-tests')
.where('geopoint', '>=', new firebase.native.firestore.GeoPoint(-1, -1))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 1);
});
});
it('correctly handles <= float values', () => {
return firebase.native.firestore()
.collection('collection-tests')
@ -470,6 +500,194 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
});
it('correctly handles limit', async () => {
const collectionTests = firebase.native.firestore().collection('collection-tests2');
await Promise.all([
collectionTests.doc('col1').set(COL_1),
collectionTests.doc('col2').set({ ...COL_1, daz: 234 }),
collectionTests.doc('col3').set({ ...COL_1, daz: 234 }),
collectionTests.doc('col4').set({ ...COL_1, daz: 234 }),
collectionTests.doc('col5').set({ ...COL_1, daz: 234 }),
]);
return collectionTests.limit(3)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
return cleanCollection(collectionTests);
});
});
});
context('cursors', () => {
let collectionTests;
before(async () => {
collectionTests = firebase.native.firestore().collection('collection-tests2');
await Promise.all([
collectionTests.doc('col1').set({ ...COL_1, foo: 'bar0' }),
collectionTests.doc('col2').set({ ...COL_1, foo: 'bar1', daz: 234, timestamp: new Date(2017, 2, 11, 10, 0, 0) }),
collectionTests.doc('col3').set({ ...COL_1, foo: 'bar2', daz: 345, timestamp: new Date(2017, 2, 12, 10, 0, 0) }),
collectionTests.doc('col4').set({ ...COL_1, foo: 'bar3', daz: 456, timestamp: new Date(2017, 2, 13, 10, 0, 0) }),
collectionTests.doc('col5').set({ ...COL_1, foo: 'bar4', daz: 567, timestamp: new Date(2017, 2, 14, 10, 0, 0) }),
]);
});
context('endAt', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').endAt(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234, 345],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').endAt(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234, 345],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').endAt('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234, 345],
);
});
});
});
context('endBefore', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').endBefore(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').endBefore(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').endBefore('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[123, 234],
);
});
});
});
context('startAt', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').startAt(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[345, 456, 567],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').startAt(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[345, 456, 567],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').startAt('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 3);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[345, 456, 567],
);
});
});
});
context('startAfter', () => {
it('handles dates', () => {
return collectionTests.orderBy('timestamp').startAfter(new Date(2017, 2, 12, 10, 0, 0))
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[456, 567],
);
});
});
it('handles numbers', () => {
return collectionTests.orderBy('daz').startAfter(345)
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[456, 567],
);
});
});
it('handles strings', () => {
return collectionTests.orderBy('foo').startAfter('bar2')
.get()
.then((querySnapshot) => {
should.equal(querySnapshot.size, 2);
should.deepEqual(
querySnapshot.docs.map(doc => doc.data().daz),
[456, 567],
);
});
});
});
after(() => {
return cleanCollection(collectionTests);
});
});
});
}

View File

@ -14,7 +14,9 @@ export const COL_1 = {
daz: 123,
foo: 'bar',
gaz: 12.1234567,
geopoint: new firebase.native.firestore.GeoPoint(0, 0),
naz: null,
timestamp: new Date(2017, 2, 10, 10, 0, 0),
};
export const DOC_1 = { name: 'doc1' };
@ -65,7 +67,7 @@ suite.addTests(firestoreTestSuite);
export default suite;
/* HELPER FUNCTIONS */
async function cleanCollection(collection) {
export async function cleanCollection(collection) {
const collectionTestsDocs = await collection.get();
const tasks = [];
collectionTestsDocs.forEach(doc => tasks.push(doc.ref.delete()));