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 for aggregate queries including sum() & average() #8115

Merged
merged 28 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
47b1416
feat(firestore): support for aggregate queries including `sum()` & `a…
russellwheatley Nov 4, 2024
c92547e
feat(firestore, android): working version of aggregate query
russellwheatley Nov 5, 2024
7f01f41
feat: iOS implementation of aggregate queries
russellwheatley Nov 5, 2024
8203f96
test: getAggregateFromServer()
russellwheatley Nov 6, 2024
5727264
test: update e2e tests
russellwheatley Nov 6, 2024
988d7af
chore: improve typing
russellwheatley Nov 6, 2024
4264338
chore: format
russellwheatley Nov 6, 2024
fa1a1e8
chore: rm assertions
russellwheatley Nov 6, 2024
8b76a9b
chore: format
russellwheatley Nov 6, 2024
9cd5ebc
feat: 'other' platform support
russellwheatley Nov 6, 2024
5f54a16
tes: fix test scopes
russellwheatley Nov 6, 2024
8db15d5
fix: firestore lite has different name for API
russellwheatley Nov 6, 2024
5c38c5b
test: ensure exposed to end user
russellwheatley Nov 6, 2024
148cfb1
test: fix broken tests
russellwheatley Nov 7, 2024
de34cc2
fix(android): allow null value for average
russellwheatley Nov 7, 2024
bb76c4d
chore: fix typo
russellwheatley Nov 7, 2024
9f8542b
fix(firestore, android): send null errors through promise reject path
mikehardy Nov 7, 2024
f66838f
test: update aggregate query to see what happens with float handling
russellwheatley Nov 8, 2024
056f45c
fix: update exception handling iOS
russellwheatley Nov 8, 2024
b442827
chore: AggregateQuerySnapshot type update
russellwheatley Nov 8, 2024
f5a2fe5
fix: return after promise rejection
russellwheatley Nov 8, 2024
5bdd26a
fix: android, fieldPath can be null for count. fix promise.reject
russellwheatley Nov 8, 2024
97124c9
chore: remove tag
russellwheatley Nov 8, 2024
eb65fd8
test: edge cases for aggregate queries
russellwheatley Nov 12, 2024
01e2d94
chore: remove only() for test
russellwheatley Nov 12, 2024
d18a67c
test: update what test produces
russellwheatley Nov 12, 2024
a89ca19
test: correct return type expected
russellwheatley Nov 13, 2024
cb54669
test: ensure aggregate fields are exposed to end user
russellwheatley Nov 13, 2024
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
5 changes: 5 additions & 0 deletions packages/firestore/__tests__/firestore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import firestore, {
firebase,
Filter,
getFirestore,
getAggregateFromServer,
addDoc,
doc,
collection,
Expand Down Expand Up @@ -651,6 +652,10 @@ describe('Firestore', function () {
it('`enablePersistentCacheIndexAutoCreation` is properly exposed to end user', function () {
expect(enablePersistentCacheIndexAutoCreation).toBeDefined();
});

it('`getAggregateFromServer` is properly exposed to end user', function () {
expect(getAggregateFromServer).toBeDefined();
});
});

describe('FirestorePersistentCacheIndexManager', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*
*/

import static com.google.firebase.firestore.AggregateField.average;
import static com.google.firebase.firestore.AggregateField.sum;
import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreCommon.rejectPromiseFirestoreException;
import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreSerialize.snapshotToWritableMap;
import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.getFirestoreForApp;
Expand All @@ -28,6 +30,7 @@
import com.google.firebase.firestore.*;
import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
import io.invertase.firebase.common.ReactNativeFirebaseModule;
import java.util.ArrayList;

public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFirebaseModule {
private static final String SERVICE_NAME = "FirestoreCollection";
Expand Down Expand Up @@ -193,6 +196,114 @@ public void collectionCount(
});
}

@ReactMethod
public void aggregateQuery(
String appName,
String databaseId,
String path,
String type,
ReadableArray filters,
ReadableArray orders,
ReadableMap options,
ReadableArray aggregateQueries,
Promise promise) {
FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId);
ReactNativeFirebaseFirestoreQuery firestoreQuery =
new ReactNativeFirebaseFirestoreQuery(
appName,
databaseId,
getQueryForFirestore(firebaseFirestore, path, type),
filters,
orders,
options);

ArrayList<AggregateField> aggregateFields = new ArrayList<>();

for (int i = 0; i < aggregateQueries.size(); i++) {
ReadableMap aggregateQuery = aggregateQueries.getMap(i);
String aggregateType = aggregateQuery.getString("aggregateType");
if (aggregateType == null) aggregateType = "";
String fieldPath = aggregateQuery.getString("field");

switch (aggregateType) {
case "count":
aggregateFields.add(AggregateField.count());
break;
case "sum":
aggregateFields.add(AggregateField.sum(fieldPath));
break;
case "average":
aggregateFields.add(AggregateField.average(fieldPath));
break;
default:
rejectPromiseWithCodeAndMessage(
promise, "firestore/invalid-argument", "Invalid AggregateType: " + aggregateType);
return;
}
}
AggregateQuery firestoreAggregateQuery =
firestoreQuery.query.aggregate(
aggregateFields.get(0),
aggregateFields.subList(1, aggregateFields.size()).toArray(new AggregateField[0]));

firestoreAggregateQuery
.get(AggregateSource.SERVER)
.addOnCompleteListener(
task -> {
if (task.isSuccessful()) {
WritableMap result = Arguments.createMap();
AggregateQuerySnapshot snapshot = task.getResult();

for (int k = 0; k < aggregateQueries.size(); k++) {
ReadableMap aggQuery = aggregateQueries.getMap(k);
String aggType = aggQuery.getString("aggregateType");
if (aggType == null) aggType = "";
String field = aggQuery.getString("field");
String key = aggQuery.getString("key");

if (key == null) {
rejectPromiseWithCodeAndMessage(
promise, "firestore/invalid-argument", "key may not be null");
return;
}

switch (aggType) {
case "count":
result.putDouble(key, Long.valueOf(snapshot.getCount()).doubleValue());
break;
case "sum":
Number sum = (Number) snapshot.get(sum(field));
if (sum == null) {
rejectPromiseWithCodeAndMessage(
promise, "firestore/unknown", "sum unexpectedly null");
return;
}
result.putDouble(key, sum.doubleValue());
break;
case "average":
Number average = snapshot.get(average(field));
if (average == null) {
result.putNull(key);
} else {
result.putDouble(key, average.doubleValue());
}
break;
default:
rejectPromiseWithCodeAndMessage(
promise,
"firestore/invalid-argument",
"Invalid AggregateType: " + aggType);
return;
}
}

promise.resolve(result);
} else {
rejectPromiseFirestoreException(promise, task.getException());
}
});
}

@ReactMethod
public void collectionGet(
String appName,
Expand Down
Loading
Loading