Skip to content

Commit

Permalink
feat(firestore, android): working version of aggregate query
Browse files Browse the repository at this point in the history
  • Loading branch information
russellwheatley committed Nov 5, 2024
1 parent 47b1416 commit c92547e
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 40 deletions.
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 Down Expand Up @@ -205,7 +207,8 @@ public void aggregateQuery(
ReadableArray filters,
ReadableArray orders,
ReadableMap options,
ReadableArray aggregateQueries
ReadableArray aggregateQueries,
Promise promise
){
FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId);
ReactNativeFirebaseFirestoreQuery firestoreQuery =
Expand All @@ -216,57 +219,76 @@ public void aggregateQuery(
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");
String fieldPath = aggregateQuery.getString("fieldPath");
if (aggregateType && fieldPath) {
String fieldPath = aggregateQuery.getString("field");

assert aggregateType != null;
switch (aggregateType) {
case "count":
aggregateFields.add(AggregateField.count(fieldPath));
aggregateFields.add(AggregateField.count());
break;
case "sum":
assert fieldPath != null;
aggregateFields.add(AggregateField.sum(fieldPath));
break;
case "average":
aggregateFields.add(AggregateField.avg(fieldPath));
assert fieldPath != null;
aggregateFields.add(AggregateField.average(fieldPath));
break;
default:
break;
throw new Error("Invalid AggregateType: " + aggregateType);
}
}
}
AggregateQuery firestoreAggregateQuery = firestoreQuery.query.aggregate(aggregateFields.get(0),
aggregateFields.subList(1, aggregateFields.size()).toArray(new AggregateField[0]));

AggregateQuery aggregateQuery = firestoreQuery.query.aggregate(aggregateFields);
aggregateQuery
firestoreAggregateQuery
.get(AggregateSource.SERVER)
.addOnCompleteListener(
task -> {
if (task.isSuccessful()) {
WritableMap result = Arguments.createMap();
AggregateQuerySnapshot snapshot = task.getResult();
aggregateFields.forEach(aggregateField -> {
switch (aggregateField.getOperator()) {

for (int k = 0; k < aggregateQueries.size(); k++) {
ReadableMap aggQuery = aggregateQueries.getMap(k);
String aggType = aggQuery.getString("aggregateType");
String field = aggQuery.getString("field");
String key = aggQuery.getString("key");
assert key != null;
assert aggType != null;
switch (aggType) {
case "count":
result.putDouble(aggregateField.getFieldPath(), Long.valueOf(snapshot.getCount()).doubleValue());
result.putDouble(key, Long.valueOf(snapshot.getCount()).doubleValue());
break;
case "sum":
result.putDouble(aggregateField.getFieldPath(), snapshot.getSum(aggregateField.getFieldPath()));
assert field != null;
Number sum = (Number) snapshot.get(sum(field));
assert sum != null;
result.putDouble(key, sum.doubleValue());
break;
case "average":
result.putDouble(aggregateField.getFieldPath(), snapshot.getAverage(aggregateField.getFieldPath()));
assert field != null;
Number average = snapshot.get(average(field));
assert average != null;
result.putDouble(key, average.doubleValue());
break;
default:
break;
throw new Error("Invalid AggregateType: " + aggType);
}
}

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

@ReactMethod
Expand Down
13 changes: 9 additions & 4 deletions packages/firestore/lib/FirestoreAggregate.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,23 @@ export class FirestoreAggregateQuery {
this._modifiers.orders,
this._modifiers.options,
)
.then(data => new FirestoreAggregateQuerySnapshot(this._query, data));
.then(data => new FirestoreAggregateQuerySnapshot(this._query, data, true));

Check warning on line 41 in packages/firestore/lib/FirestoreAggregate.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/FirestoreAggregate.js#L41

Added line #L41 was not covered by tests
}
}

export class FirestoreAggregateQuerySnapshot {
constructor(query, data) {
constructor(query, data, isGetCountFromServer) {

Check warning on line 46 in packages/firestore/lib/FirestoreAggregate.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/FirestoreAggregate.js#L46

Added line #L46 was not covered by tests
this._query = query;
this._data = data;
this._isGetCountFromServer = isGetCountFromServer;

Check warning on line 49 in packages/firestore/lib/FirestoreAggregate.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/FirestoreAggregate.js#L49

Added line #L49 was not covered by tests
}

data() {
return { count: this._data.count };
if (this._isGetCountFromServer) {
return { count: this._data.count };
} else {
return { ...this._data };

Check warning on line 56 in packages/firestore/lib/FirestoreAggregate.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/FirestoreAggregate.js#L54-L56

Added lines #L54 - L56 were not covered by tests
}
}
}

Expand Down Expand Up @@ -80,7 +85,7 @@ export function fieldPathFromArgument(path) {
if (path instanceof FirestoreFieldPath) {
return path;

Check warning on line 86 in packages/firestore/lib/FirestoreAggregate.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/FirestoreAggregate.js#L86

Added line #L86 was not covered by tests
} else if (typeof path === 'string') {
return fromDotSeparatedString(methodName, path);
return fromDotSeparatedString(path);
} else {
throw new Error('Field path arguments must be of type `string` or `FieldPath`');

Check warning on line 90 in packages/firestore/lib/FirestoreAggregate.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/FirestoreAggregate.js#L88-L90

Added lines #L88 - L90 were not covered by tests
}
Expand Down
46 changes: 26 additions & 20 deletions packages/firestore/lib/modular/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
*/

import { firebase } from '../index';
import { AggregateField, AggregateType } from '../FirestoreAggregate';
import FirestorePath from '../FirestorePath';
import {
FirestoreAggregateQuerySnapshot,
AggregateField,
AggregateType,
fieldPathFromArgument,
} from '../FirestoreAggregate';

/**
* @param {FirebaseApp?} app
Expand Down Expand Up @@ -198,37 +202,39 @@ export async function getAggregateFromServer(query, aggregateSpec) {
const aggregateQueries = [];
for (const key in aggregateSpec) {

Check warning on line 203 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L201-L203

Added lines #L201 - L203 were not covered by tests
if (aggregateSpec.hasOwnProperty(key)) {
const value = aggregateSpec[key];
const aggregateField = aggregateSpec[key];

Check warning on line 205 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L205

Added line #L205 was not covered by tests

if (value instanceof AggregateField) {
switch (value.aggregateType) {
if (aggregateField instanceof AggregateField) {
switch (aggregateField.aggregateType) {
case AggregateType.AVG:
case AggregateType.SUM:
case AggregateType.COUNT:
const aggregateQuery = {

Check warning on line 212 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L209-L212

Added lines #L209 - L212 were not covered by tests
aggregateType: value.aggregateType,
// TODO - how is this sent over the wire? Think it is serialized automatically
field: value.fieldPath,
aggregateType: aggregateField.aggregateType,
field: aggregateField.fieldPath === null ? null : aggregateField.fieldPath._toPath(),

Check warning on line 214 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L214

Added line #L214 was not covered by tests
key,
};
aggregateQueries.push(aggregateQuery);
break;
default:
throw new Error(

Check warning on line 220 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L217-L220

Added lines #L217 - L220 were not covered by tests
`"AggregateField" has an an unknown "AggregateType" : ${value.aggregateType}`,
`"AggregateField" has an an unknown "AggregateType" : ${aggregateField.aggregateType}`,
);
}
}
}
}

return query._firestore.native.aggregateQuery(
query._collectionPath.relativeName,
query._modifiers.type,
query._modifiers.filters,
query._modifiers.orders,
query._modifiers.options,
aggregateQueries,
);
return query._firestore.native

Check warning on line 228 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L228

Added line #L228 was not covered by tests
.aggregateQuery(
query._collectionPath.relativeName,
query._modifiers.type,
query._modifiers.filters,
query._modifiers.orders,
query._modifiers.options,
aggregateQueries,
)
.then(data => new FirestoreAggregateQuerySnapshot(query, data, false));

Check warning on line 237 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L237

Added line #L237 was not covered by tests
}

/**
Expand All @@ -237,7 +243,7 @@ export async function getAggregateFromServer(query, aggregateSpec) {
* @param field Specifies the field to sum across the result set.
*/
export function sum(field) {
return new AggregateField(AggregateType.SUM, FirestorePath.fromName(field));
return new AggregateField(AggregateType.SUM, fieldPathFromArgument(field));

Check warning on line 246 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L245-L246

Added lines #L245 - L246 were not covered by tests
}

/**
Expand All @@ -246,15 +252,15 @@ export function sum(field) {
* @param field Specifies the field to average across the result set.
*/
export function average(field) {
return new AggregateField(AggregateType.AVG, FirestorePath.fromName(field));
return new AggregateField(AggregateType.AVG, fieldPathFromArgument(field));

Check warning on line 255 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L254-L255

Added lines #L254 - L255 were not covered by tests
}

/**
* Create an AggregateField object that can be used to compute the count of
* documents in the result set of a query.
*/
export function count() {
return new AggregateField(AggregateType.COUNT);
return new AggregateField(AggregateType.COUNT, null);

Check warning on line 263 in packages/firestore/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/firestore/lib/modular/index.js#L262-L263

Added lines #L262 - L263 were not covered by tests
}

/**
Expand Down

0 comments on commit c92547e

Please sign in to comment.