-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Firestore: Adding Collection Group queries #4652
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #4652 +/- ##
============================================
+ Coverage 49.67% 49.68% +<.01%
- Complexity 22300 22308 +8
============================================
Files 2238 2238
Lines 221344 221337 -7
Branches 24079 24069 -10
============================================
+ Hits 109949 109967 +18
+ Misses 102937 102924 -13
+ Partials 8458 8446 -12
Continue to review full report at Codecov.
|
rsgowman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm with a bunch of nits.
|
|
||
| QueryOptions that = (QueryOptions) o; | ||
|
|
||
| if (allDescendants != that.allDescendants) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Is there reasoning for this order? If not, then consider reordering to match the declaration order. (If certain variables are more likely to differ, that might be a legit reason to prioritize checking those ones in the presence of a tight loop.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is auto-generated code by IntelliJ (and not by AutoValue).
| fieldFilters = new ArrayList<>(); | ||
| fieldOrders = new ArrayList<>(); | ||
| fieldProjections = new ArrayList<>(); | ||
| protected static class QueryOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless there's a reason not to, you should probably make this class final.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated this class to use AutoValue.
|
|
||
| if (allDescendants != that.allDescendants) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider refactoring to look like this: http://go/java-practices/equals#impl
Specifically:
a) optionally drop the this==o check unless you think it's useful.
b) use instanceof instead of getClass. (Less relevant if you mark this class final.)
c) Instead of using 3 lines per variable, just use ~1 per variable, i.e.:
return (foo == that.foo
&& bar == that.bar
&& ...);
Entirely optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, this is auto-generated and now replaced by AutoValue.
| fieldFilters = new ArrayList<>(); | ||
| fieldOrders = new ArrayList<>(); | ||
| fieldProjections = new ArrayList<>(); | ||
| protected static class QueryOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just use go/autovalue here? (public link: https://github.com/google/auto/blob/master/value/userguide/index.md)
If we can, it'd cut this down by quite a lot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to use AutoValue as suggested. I personally don't consider this an improvement, as it adds a lot of indirection. QueryOptions was treated by me like a value struct, but its immutability was merely by design. With AutoValue and with the migration to ImmutableList, this is now enforced in code.
| fieldProjections = new ArrayList<>(); | ||
| protected static class QueryOptions { | ||
|
|
||
| ResourcePath parentPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These instance variables should all be final too. http://go/java-practices/final#fields.
(Or at least final where possible. I'm haven't checked to see if these are reassigned or not. Hopefully mostly not.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They were reassigned as part of my "builder-light" pattern:
Options newOptions = new Options(oldOptions);
newOptions.foo = bar;
This is no longer applicable.
| Query(FirestoreImpl firestore, ResourcePath path) { | ||
| this(firestore, path, new QueryOptions()); | ||
| this.firestore = firestore; | ||
| this.options = new QueryOptions(path.getParent(), path.getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable, but consider delegating to the private ctor anyway, i.e.:
Query(firestore, path) {
this(firestore, new QueryOptoins(path.getParent(), path.getId());
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
|
||
| this.firestore = firestore; | ||
| this.path = path; | ||
| this.options = queryOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, consider delegating to the primary ctor. Optional.
Also note that this ctor (and the other one above) could also be rephrased as static factory methods. (Which would allow you to rename them to something other than 'Query'. Whether that's useful or not is up to you.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this change, which is now possible thanks to the QueryOptions.Builder. Without it, I had no way to initialize allDescendants.
I opted not to make these factory methods since CollectionReference inherits from Query and I like the consistency of having two constructors rather than one constructor and one factory method.
| Query query = firestoreMock.collectionGroup("foo/bar"); | ||
| fail(); | ||
| } catch (IllegalArgumentException e) { | ||
| assertEquals( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine. Personally, I find it slightly over-specified. (I'd just remove the assertEquals entirely.) Feel free to leave as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged.
|
|
||
| assertEquals(begin(), requests.get(0)); | ||
| assertEquals(query(TRANSACTION_ID), requests.get(1)); | ||
| assertEquals(query(TRANSACTION_ID, false), requests.get(1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've forgotten what the second parameter is (and am too lazy to look it up.) Consider one of the options here:
http://go/java-practices/boolean-parameters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added /* allDescendants= */ as a comment. Thanks
schmidt-sebastian
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I addressed your feedback and bit the bullet by moving to AutoValue.
| fieldFilters = new ArrayList<>(); | ||
| fieldOrders = new ArrayList<>(); | ||
| fieldProjections = new ArrayList<>(); | ||
| protected static class QueryOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated this class to use AutoValue.
| fieldFilters = new ArrayList<>(); | ||
| fieldOrders = new ArrayList<>(); | ||
| fieldProjections = new ArrayList<>(); | ||
| protected static class QueryOptions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to use AutoValue as suggested. I personally don't consider this an improvement, as it adds a lot of indirection. QueryOptions was treated by me like a value struct, but its immutability was merely by design. With AutoValue and with the migration to ImmutableList, this is now enforced in code.
| fieldProjections = new ArrayList<>(); | ||
| protected static class QueryOptions { | ||
|
|
||
| ResourcePath parentPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They were reassigned as part of my "builder-light" pattern:
Options newOptions = new Options(oldOptions);
newOptions.foo = bar;
This is no longer applicable.
|
|
||
| QueryOptions that = (QueryOptions) o; | ||
|
|
||
| if (allDescendants != that.allDescendants) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is auto-generated code by IntelliJ (and not by AutoValue).
|
|
||
| if (allDescendants != that.allDescendants) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, this is auto-generated and now replaced by AutoValue.
| Query(FirestoreImpl firestore, ResourcePath path) { | ||
| this(firestore, path, new QueryOptions()); | ||
| this.firestore = firestore; | ||
| this.options = new QueryOptions(path.getParent(), path.getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
|
||
| this.firestore = firestore; | ||
| this.path = path; | ||
| this.options = queryOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this change, which is now possible thanks to the QueryOptions.Builder. Without it, I had no way to initialize allDescendants.
I opted not to make these factory methods since CollectionReference inherits from Query and I like the consistency of having two constructors rather than one constructor and one factory method.
| Query query = firestoreMock.collectionGroup("foo/bar"); | ||
| fail(); | ||
| } catch (IllegalArgumentException e) { | ||
| assertEquals( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged.
|
|
||
| assertEquals(begin(), requests.get(0)); | ||
| assertEquals(query(TRANSACTION_ID), requests.get(1)); | ||
| assertEquals(query(TRANSACTION_ID, false), requests.get(1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added /* allDescendants= */ as a comment. Thanks
a9946bb to
a6d948b
Compare
|
This is no longer blocked and needs sign off from the Yoshi team. |
...ts/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java
Show resolved
Hide resolved
chingor13
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a question on whether or not to add a getter method. Otherwise, LGTM.
| QueryTarget.newBuilder() | ||
| .setStructuredQuery(query.buildQuery()) | ||
| .setParent(query.getResourcePath().getParent().getName()) | ||
| .setParent(query.options.getParentPath().getName()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason for direct field access vs. a getter? We provide getFirestore() on query, but not getOptions().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
options is purely an internal field that is only used inside the client. We could still use a getOptions() here, but I felt that making it package-private was safe enough.
CollectionGroup queries add support for queries to Firestore that query documents by collection ID rather than by collection path. With this feature, users can now query for documents in collections that share a name, no matter where these collection are located.
This the server port of firebase/firebase-android-sdk#233, meaning there is less input validation (since the backend does most of it for us) and there is no local logic to execute these queries.
Note: This PR is blocked on a backend release.