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

Fix issue #5274 on RestQuery.each and relations #5276

Merged
merged 5 commits into from
Jan 4, 2019
Merged

Fix issue #5274 on RestQuery.each and relations #5276

merged 5 commits into from
Jan 4, 2019

Conversation

percypyan
Copy link
Contributor

@percypyan percypyan commented Jan 3, 2019

Closes: #5274

The RestQuery.prototype.each method was going through all results by adding a filter on objectId field of the concerned class when the results length exceed the limit. The problem was that if any constraint has been done on objectId field, it was overridden. That was the case for the all relation queries (like the _Roles of a user for any user authenticated queries).
To fix this, replace the assignation of an arbitrary object by the merge of the existing constraint with the new one.

The fix provided allows .each to only query on roles for this specific user instead of all roles

@codecov
Copy link

codecov bot commented Jan 3, 2019

Codecov Report

Merging #5276 into master will decrease coverage by 0.02%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #5276      +/-   ##
==========================================
- Coverage   93.93%   93.91%   -0.03%     
==========================================
  Files         123      123              
  Lines        8975     8975              
==========================================
- Hits         8431     8429       -2     
- Misses        544      546       +2
Impacted Files Coverage Δ
src/RestQuery.js 96.11% <100%> (ø) ⬆️
src/RestWrite.js 92.88% <0%> (-0.37%) ⬇️

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 cc0d769...a3c9330. Read the comment docs.

Copy link
Contributor

@flovilmart flovilmart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the query has the objectId constraint then finished should be ‘true’ and we should never override the objectId part of the RESTWhere?

@percypyan
Copy link
Contributor Author

For a simple equals yes, but for $in, it’s an array that can contains more items than the limit !

@percypyan
Copy link
Contributor Author

And it’s the case the when you have a bunch of roles for a user ! (More than the default limit of 100 in our case)

@dplewis
Copy link
Member

dplewis commented Jan 3, 2019

After a quick glance and removing your fix

but for $in, it’s an array that can contains more items than the limit

The first loop returns a result

{ objectId: { '$in': [ '...' ] } }

The second loop returns a result

{ objectId: { '$gt': '...' } }

You could stop at the first loop since your limit is 1

finished = results.length < restOptions.limit; change to <= also solves your issue.

There aren't a lot of tests for RestQuery.each so maybe more are needed were the number of results don't match the limit.

@percypyan
Copy link
Contributor Author

Hum ok, I see what you mean. But in each, if we look at the current finished condition, and look at the fact that this function is used to retrieve the roles of a user in the purpose to check the read and/or write access on the result of a query, I’ve assumed that the limit here is only used to fragment the request in smaller pieces.
If it’s not, what is the purpose of a continueWhile ? Only one request is needed to retrieve all the results for that limit, isn’t it ?
But maybe I’m wrong, in what case I think that something is probably broken somewhere else since the roles of an user cannot be properly retrieved if there no other filter than the $gt

@percypyan
Copy link
Contributor Author

percypyan commented Jan 4, 2019

Oh ! Ok, maybe you just mean I haven’t write enough tests ?
Sorry, I think I’ve misunderstood the purpose of your comment at the first read 😋

@flovilmart
Copy link
Contributor

I believe what @dplewis refers to is that you mention issues with users and roles, yet no test show the issue with such users and roles.

@dplewis
Copy link
Member

dplewis commented Jan 4, 2019

After playing around a bit, either something is broken or will be broken. How did you discover this? Did you have to use RestQuery.each directly or can you reproduce with the JS SDK?

@flovilmart
Copy link
Contributor

It’s likely the bug can be found if the users appear in many many roles, perhaps there’s a bug there.

@percypyan
Copy link
Contributor Author

I discovered the bug cause we have many roles by users (2000+ for the user with wich I have discovered the issue). The query through the Parse js SDK to our parse server timed out each time and I finally discovered that this issue existed. The user tried to retrieve a lot more roles from the database and of course, timed out

@percypyan
Copy link
Contributor Author

The test I've written is the simplest way to reproduce the bug without creating a user and many many roles. But in fact, exactly the same thing happen for the two situation: a relation query is converted into $in query by RestQuery.execute and then overriden at the end of the first iteration (except if there is only one) of the RestQuery.each. With the limit of 1, for a relation A-B, only 2 A objects and 2 B objects are required to make one of 2 query fail each time.

For A1 <-> B1 and A2 <-> B2.
For A1.id > A2.id.

  1. Query all A related to B1: will pass because { $gt: A1.id } will not include A2.id either.
  2. Query all A related to B2: will not pass because { $gt: A2.id } will return A1 which is not expected.

And if A1.id < A2.id query 1 will fail and query 2 will pass.

@dplewis
Copy link
Member

dplewis commented Jan 4, 2019

Here is a failing test (modified from Auth.spec.js) but passes with this fix.

fit('should load all roles for different users with config', async () => {
      const rolesNumber = 100;
      const user = new Parse.User();
      await user.signUp({
        username: 'hello',
        password: 'password',
      });
      const user2 = new Parse.User();
      await user2.signUp({
        username: 'world',
        password: '1234',
      });
      expect(user.getSessionToken()).not.toBeUndefined();
      const userAuth = await getAuthForSessionToken({
        sessionToken: user.getSessionToken(),
        config: Config.get('test'),
      });
      const user2Auth = await getAuthForSessionToken({
        sessionToken: user2.getSessionToken(),
        config: Config.get('test'),
      });
      const roles = [];
      for (let i = 0; i < rolesNumber; i += 1){
        const acl = new Parse.ACL();
        const role = new Parse.Role("roleloadtest" + i, acl);
        const role2 = new Parse.Role("role2loadtest" + i, acl);
        role.getUsers().add([user]);
        role2.getUsers().add([user2]);
        roles.push(role.save());
        roles.push(role2.save());
      }
      const savedRoles = await Promise.all(roles);
      expect(savedRoles.length).toBe(rolesNumber * 2);
      const cloudRoles = await userAuth.getRolesForUser();
      const cloudRoles2 = await user2Auth.getRolesForUser();
      expect(cloudRoles.length).toBe(rolesNumber);
      expect(cloudRoles2.length).toBe(rolesNumber);
    });

The fix provided allows .each to only query on roles for this specific user instead of all roles

@percypyan
Copy link
Contributor Author

Ok @dplewis, thanks I'm adding it.


const config = Config.get('test');

/* Two query needed since objectId are sorted and we can't know wich one
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typos and formatting in this comment needs to be fixed

@percypyan
Copy link
Contributor Author

All ok for you? @dplewis

}
const savedRoles = await Promise.all(roles);
expect(savedRoles.length).toBe(rolesNumber);
const cloudRoles = await userAuth.getRolesForUser();
expect(cloudRoles.length).toBe(rolesNumber);
});

fit('should load all roles for different users with config', async () => {
Copy link
Member

@dplewis dplewis Jan 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove fit. That makes only this test run and ignore all other.

Run it locally also

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh shit, I've ran it locally but forget to remove the fit... Sorry

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its cool

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

@dplewis
Copy link
Member

dplewis commented Jan 4, 2019

@flovilmart How does this look?

@flovilmart flovilmart merged commit 9f2fc88 into parse-community:master Jan 4, 2019
@flovilmart
Copy link
Contributor

Thanks for this!

@percypyan percypyan deleted the bugfix/each#5274 branch January 5, 2019 17:43
UnderratedDev pushed a commit to UnderratedDev/parse-server that referenced this pull request Mar 21, 2020
…-community#5276)

* Add test on RestQuery.each with relation

* Fix the failing test for RestQuery.each and relations

* Add test for getRolesForUser

* Fix format for comment

* Remove extra fit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RestQuery.each override queries constraints on objectId field
3 participants