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): Firestore Filter instance. Use Filter, Filter.or() & Filter.and() in Firestore queries. #7045

Merged
merged 30 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3f0b598
feat: first draft implementation of Filter
russellwheatley Apr 13, 2023
3a80cf4
feat: iOS implementation for AND filter
russellwheatley Apr 13, 2023
8171b0d
chore: rm env var that stops logs showing on test app
russellwheatley Apr 13, 2023
cd68602
feat: android implementation of AND query
russellwheatley Apr 13, 2023
4cd16d0
test: Filter AND e2e tests
russellwheatley Apr 14, 2023
140a3ea
fix: java mistake
russellwheatley Apr 14, 2023
bd99e37
chore: Filter JS implementation update
russellwheatley Apr 14, 2023
1f587f1
fix: iOS implementation
russellwheatley Apr 14, 2023
3e612a0
feat: start OR query implementation
russellwheatley Apr 14, 2023
215be0d
Update java implementation for OR query
russellwheatley Apr 14, 2023
c337b2a
test: update e2e tests
russellwheatley Apr 14, 2023
42bbb06
test: where filters
russellwheatley Apr 14, 2023
6410ca9
Restore packages/firestore/e2e/Query/where.e2e.js
russellwheatley Apr 14, 2023
6f65c58
test: update exception message
russellwheatley Apr 17, 2023
7479429
refactor: update implementation to handle recursion
russellwheatley Apr 17, 2023
3d31b2a
refactor: update iOS implementation for OR query
russellwheatley Apr 17, 2023
5824cff
test: update e2e tests for Filter
russellwheatley Apr 17, 2023
47117f9
test: update test
russellwheatley Apr 17, 2023
808cdd8
ios format
russellwheatley Apr 17, 2023
b227154
format: android
russellwheatley Apr 17, 2023
1865ff2
Merge branch 'main' into firestore-or-query
russellwheatley Apr 17, 2023
9dee0c4
docs: Firestore Filter documentation
russellwheatley Apr 17, 2023
798dab6
feat: stop nested OR Filters
russellwheatley Apr 25, 2023
0f4ed35
chore: rm setInterval function
russellwheatley Apr 25, 2023
a09a472
test: fix exception message
russellwheatley Apr 25, 2023
a8e4df4
use example.com as a domain in documentation per RFC
mikehardy Apr 26, 2023
bb152a5
result of `yarn lint:android`
mikehardy Apr 26, 2023
0b17631
chore: types
russellwheatley Apr 26, 2023
ba25dad
update types
russellwheatley Apr 26, 2023
20e9c8d
improve types for Filter
russellwheatley Apr 27, 2023
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
38 changes: 37 additions & 1 deletion docs/firestore/usage/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,43 @@ firestore()
To learn more about all of the querying capabilities Cloud Firestore has to offer, view the
[Firebase documentation](https://firebase.google.com/docs/firestore/query-data/queries).

It is now possible to use the `Filter` instance to make queries. They can be used with the existing query API.
For example, you could chain like so:

```js
const snapshot = await firestore()
.collection('Users')
.where(Filter('user', '==', 'Tim'))
.where('email', '==', 'tim@example.com')
.get();
```

You can use the `Filter.and()` static method to make logical AND queries:

```js
const snapshot = await firestore()
.collection('Users')
.where(Filter.and(Filter('user', '==', 'Tim'), Filter('email', '==', 'tim@example.com')))
.get();
```

You can use the `Filter.or()` static method to make logical OR queries:

```js
const snapshot = await firestore()
.collection('Users')
.where(
Filter.or(
Filter.and(Filter('user', '==', 'Tim'), Filter('email', '==', 'tim@example.com')),
Filter.and(Filter('user', '==', 'Dave'), Filter('email', '==', 'dave@example.com')),
),
)
.get();
```

For an understanding of what queries are possible, please consult the query limitation documentation on the official
[Firebase Firestore documentation](https://firebase.google.com/docs/firestore/query-data/queries#limits_on_or_queries).

#### Limiting

To limit the number of documents returned from a query, use the `limit` method on a collection reference:
Expand Down Expand Up @@ -317,7 +354,6 @@ with an ID of `DEF`.
Cloud Firestore does not support the following types of queries:

- Queries with range filters on different fields, as described in the previous section.
- Logical OR queries. In this case, you should create a separate query for each OR condition and merge the query results in your app.

## Writing Data

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.firestore.FieldPath;
import com.google.firebase.firestore.Filter;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.firestore.Source;
Expand Down Expand Up @@ -65,62 +66,139 @@ private void applyFilters(ReadableArray filters) {
for (int i = 0; i < filters.size(); i++) {
ReadableMap filter = filters.getMap(i);

ArrayList fieldPathArray = Objects.requireNonNull(filter).getArray("fieldPath").toArrayList();
String[] segmentArray = (String[]) fieldPathArray.toArray(new String[0]);
if (filter.hasKey("fieldPath")) {
ArrayList<Object> fieldPathArray =
Objects.requireNonNull(Objects.requireNonNull(filter).getArray("fieldPath"))
.toArrayList();
String[] segmentArray = (String[]) fieldPathArray.toArray(new String[0]);

FieldPath fieldPath = FieldPath.of(Objects.requireNonNull(segmentArray));
String operator = filter.getString("operator");
ReadableArray arrayValue = filter.getArray("value");
Object value = parseTypeMap(query.getFirestore(), Objects.requireNonNull(arrayValue));

switch (Objects.requireNonNull(operator)) {
case "EQUAL":
query = query.whereEqualTo(Objects.requireNonNull(fieldPath), value);
break;
case "NOT_EQUAL":
query = query.whereNotEqualTo(Objects.requireNonNull(fieldPath), value);
break;
case "GREATER_THAN":
query =
query.whereGreaterThan(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
case "GREATER_THAN_OR_EQUAL":
query =
query.whereGreaterThanOrEqualTo(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
case "LESS_THAN":
query =
query.whereLessThan(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
case "LESS_THAN_OR_EQUAL":
query =
query.whereLessThanOrEqualTo(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
case "ARRAY_CONTAINS":
query =
query.whereArrayContains(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
case "ARRAY_CONTAINS_ANY":
query =
query.whereArrayContainsAny(
Objects.requireNonNull(fieldPath),
Objects.requireNonNull((List<Object>) value));
break;
case "IN":
query =
query.whereIn(
Objects.requireNonNull(fieldPath),
Objects.requireNonNull((List<Object>) value));
break;
case "NOT_IN":
query =
query.whereNotIn(
Objects.requireNonNull(fieldPath),
Objects.requireNonNull((List<Object>) value));
break;
}
} else if (filter.hasKey("operator") && filter.hasKey("queries")) {
query = query.where(applyFilterQueries(filter));
}
}
}

private Filter applyFilterQueries(ReadableMap filter) {
if (filter.hasKey("fieldPath")) {
String operator =
(String) Objects.requireNonNull(Objects.requireNonNull(filter).getString("operator"));
ReadableMap fieldPathMap = Objects.requireNonNull(filter.getMap("fieldPath"));
ReadableArray segments = Objects.requireNonNull(fieldPathMap.getArray("_segments"));
int arraySize = segments.size();
String[] segmentArray = new String[arraySize];

FieldPath fieldPath = FieldPath.of(Objects.requireNonNull(segmentArray));
String operator = filter.getString("operator");
for (int i = 0; i < arraySize; i++) {
segmentArray[i] = segments.getString(i);
}
FieldPath fieldPath = FieldPath.of(segmentArray);
ReadableArray arrayValue = filter.getArray("value");

Object value = parseTypeMap(query.getFirestore(), Objects.requireNonNull(arrayValue));

switch (Objects.requireNonNull(operator)) {
switch (operator) {
case "EQUAL":
query = query.whereEqualTo(Objects.requireNonNull(fieldPath), value);
break;
return Filter.equalTo(fieldPath, value);
case "NOT_EQUAL":
query = query.whereNotEqualTo(Objects.requireNonNull(fieldPath), value);
break;
case "GREATER_THAN":
query =
query.whereGreaterThan(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
case "GREATER_THAN_OR_EQUAL":
query =
query.whereGreaterThanOrEqualTo(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
return Filter.notEqualTo(fieldPath, value);
case "LESS_THAN":
query =
query.whereLessThan(Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
return Filter.lessThan(fieldPath, value);
case "LESS_THAN_OR_EQUAL":
query =
query.whereLessThanOrEqualTo(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
return Filter.lessThanOrEqualTo(fieldPath, value);
case "GREATER_THAN":
return Filter.greaterThan(fieldPath, value);
case "GREATER_THAN_OR_EQUAL":
return Filter.greaterThanOrEqualTo(fieldPath, value);
case "ARRAY_CONTAINS":
query =
query.whereArrayContains(
Objects.requireNonNull(fieldPath), Objects.requireNonNull(value));
break;
return Filter.arrayContains(fieldPath, value);
case "ARRAY_CONTAINS_ANY":
query =
query.whereArrayContainsAny(
Objects.requireNonNull(fieldPath), Objects.requireNonNull((List<Object>) value));
break;
assert value != null;
return Filter.arrayContainsAny(fieldPath, (List<?>) value);
case "IN":
query =
query.whereIn(
Objects.requireNonNull(fieldPath), Objects.requireNonNull((List<Object>) value));
break;
assert value != null;
return Filter.inArray(fieldPath, (List<?>) value);
case "NOT_IN":
query =
query.whereNotIn(
Objects.requireNonNull(fieldPath), Objects.requireNonNull((List<Object>) value));
break;
assert value != null;
return Filter.notInArray(fieldPath, (List<?>) value);
default:
throw new Error("Invalid operator");
}
}

String operator = Objects.requireNonNull(filter).getString("operator");
ReadableArray queries =
Objects.requireNonNull(Objects.requireNonNull(filter).getArray("queries"));
ArrayList<Filter> parsedFilters = new ArrayList<>();
int arraySize = queries.size();
for (int i = 0; i < arraySize; i++) {
ReadableMap map = queries.getMap(i);
parsedFilters.add(applyFilterQueries(map));
}

if (operator.equals("AND")) {
return Filter.and(parsedFilters.toArray(new Filter[0]));
}

if (operator.equals("OR")) {
return Filter.or(parsedFilters.toArray(new Filter[0]));
}

throw new Error("Missing 'Filter' instance return");
}

private void applyOrders(ReadableArray orders) {
Expand Down
Loading