Skip to content

Conversation

@schmidt-sebastian
Copy link
Contributor

@schmidt-sebastian schmidt-sebastian commented Mar 6, 2019

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.

@schmidt-sebastian schmidt-sebastian requested a review from a team as a code owner March 6, 2019 18:27
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Mar 6, 2019
@schmidt-sebastian schmidt-sebastian added the status: blocked Resolving the issue is dependent on other work. label Mar 6, 2019
@schmidt-sebastian schmidt-sebastian changed the title Firestore: Adding Collection Group queries Firestore: Adding Collection Group queries (DO NOT MERGE) Mar 6, 2019
@codecov
Copy link

codecov bot commented Mar 6, 2019

Codecov Report

Merging #4652 into master will increase coverage by <.01%.
The diff coverage is 93.46%.

Impacted file tree graph

@@             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
Impacted Files Coverage Δ Complexity Δ
...java/com/google/cloud/firestore/FirestoreImpl.java 83.04% <100%> (+0.83%) 22 <4> (+3) ⬆️
...rc/main/java/com/google/cloud/firestore/Watch.java 91.81% <100%> (ø) 57 <0> (ø) ⬇️
...om/google/cloud/firestore/CollectionReference.java 48.78% <75%> (+1.28%) 10 <6> (+1) ⬆️
...rc/main/java/com/google/cloud/firestore/Query.java 92.53% <94.07%> (+4.8%) 90 <35> (+4) ⬆️
...src/main/java/com/google/cloud/ServiceOptions.java 41.4% <0%> (+0.88%) 26% <0%> (ø) ⬇️
...om/google/cloud/logging/MonitoredResourceUtil.java 50.92% <0%> (+0.92%) 8% <0%> (ø) ⬇️
.../java/com/google/cloud/testing/CommandWrapper.java 96.96% <0%> (+6.06%) 13% <0%> (ø) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d931fac...b873df9. Read the comment docs.

Copy link

@rsgowman rsgowman left a 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) {
Copy link

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.)

Copy link
Contributor Author

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 {
Copy link

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.

http://go/java-practices/final#classes

Copy link
Contributor Author

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;
}
Copy link

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.

Copy link
Contributor Author

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 {
Copy link

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.

Copy link
Contributor Author

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;
Copy link

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.)

Copy link
Contributor Author

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());
Copy link

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());
}

Copy link
Contributor Author

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;
Copy link

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.)

Copy link
Contributor Author

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(
Copy link

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.

Copy link
Contributor Author

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));
Copy link

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

Copy link
Contributor Author

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

Copy link
Contributor Author

@schmidt-sebastian schmidt-sebastian left a 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 {
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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;
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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;
}
Copy link
Contributor Author

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());
Copy link
Contributor Author

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;
Copy link
Contributor Author

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(
Copy link
Contributor Author

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));
Copy link
Contributor Author

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 schmidt-sebastian force-pushed the mrschmidt-collectiongroup branch from a9946bb to a6d948b Compare March 14, 2019 01:45
@sduskis sduskis added the api: bigquery Issues related to the BigQuery API. label Mar 19, 2019
@sduskis sduskis added api: firestore Issues related to the Firestore API. and removed api: bigquery Issues related to the BigQuery API. labels Apr 11, 2019
@schmidt-sebastian
Copy link
Contributor Author

This is no longer blocked and needs sign off from the Yoshi team.

@yoshi-automation yoshi-automation added the 🚨 This issue needs some love. label Apr 26, 2019
@schmidt-sebastian schmidt-sebastian changed the title Firestore: Adding Collection Group queries (DO NOT MERGE) Firestore: Adding Collection Group queries Apr 27, 2019
Copy link
Contributor

@chingor13 chingor13 left a 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())
Copy link
Contributor

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().

Copy link
Contributor Author

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.

@kolea2 kolea2 merged commit 7f6dc84 into master May 1, 2019
@sduskis sduskis deleted the mrschmidt-collectiongroup branch May 1, 2019 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: firestore Issues related to the Firestore API. cla: yes This human has signed the Contributor License Agreement. 🚨 This issue needs some love.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants