Skip to content

containedIn on an array type key #4762

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

Closed
jeremypiednoel opened this issue May 14, 2018 · 38 comments
Closed

containedIn on an array type key #4762

jeremypiednoel opened this issue May 14, 2018 · 38 comments

Comments

@jeremypiednoel
Copy link
Contributor

jeremypiednoel commented May 14, 2018

Hi Guys !

Issue Description

I'm trying to use containedIn on an array key :

const array = [1 ,2 ,3];
query.containedIn('arrayKey', array);

My key arrayKey is an array in the DB : for instance [1, 3];
My issue is the query will return objects where at least one item from array is in arrayKey and not all of them.
I'm not sure if it's the normal behavior.
If it is do you guys know a workaround ? because containsAll won't work either.
Thanks for the reading.

Steps to reproduce

Make a containedIn query on array key.

Environment Setup

  • Server
    • parse-server version : 2.7.4
    • Operating System: macOS 10.12.6 (Sierra)
    • Hardware: iMac (Retina 5K, 27-inch, Late 2015)
    • Localhost or remote server? Localhost

@flovilmart
Copy link
Contributor

This is the expected behavior as containedIn operator in mongodb behaves this way. If you’re looking for and exact match, use the equal operator

@jeremypiednoel
Copy link
Contributor Author

jeremypiednoel commented May 14, 2018

Thanks @flovilmart i'm not looking for exact match, i'm looking for arrayKey is a subarray of array

@flovilmart
Copy link
Contributor

i believe contains all should do the trick, can you provide the logs when running with VERBOSE=1?

@jeremypiednoel
Copy link
Contributor Author

jeremypiednoel commented May 14, 2018

I tried with containsAll before asking and it looks like it tries to match all array with arrayKey and not the opposite

here is the verbose

13:56:55 web.1   |  verbose: REQUEST for [GET] /io/classes/Object: {
13:56:55 web.1   |    "where": {
13:56:55 web.1   |      "offers": {
13:56:55 web.1   |        "$all": [
13:56:55 web.1   |          {
13:56:55 web.1   |            "__type": "Pointer",
13:56:55 web.1   |            "className": "Object1",
13:56:55 web.1   |            "objectId": "n2MvEYmqbh"
13:56:55 web.1   |          },
13:56:55 web.1   |          {
13:56:55 web.1   |            "__type": "Pointer",
13:56:55 web.1   |            "className": "Object1",
13:56:55 web.1   |            "objectId": "wR2Ta0KL2R"
13:56:55 web.1   |          }
13:56:55 web.1   |        ]
13:56:55 web.1   |      },
13:56:55 web.1   |      "zipcode": {
13:56:55 web.1   |        "$in": [
13:56:55 web.1   |          "11230",
13:56:55 web.1   |          "11218",
13:56:55 web.1   |        ]
13:56:55 web.1   |      },
13:56:55 web.1   |    },
13:56:55 web.1   |    "limit": 30,
13:56:55 web.1   |    "order": "time"
13:56:55 web.1   |  } method=GET, url=/io/classes/Object, user-agent=node-XMLHttpRequest, Parse/js1.11.1 (NodeJS 9.2.1), accept=*/*, content-type=text/plain, host=localhost:1337, content-length=2521, connection=close, $all=[__type=Pointer, className=Object1, objectId=n2MvEYmqbh, __type=Pointer, className=Object1, objectId=wR2Ta0KL2R], $in=[11230, 11218], limit=30, order=time
13:56:55 web.1   |  verbose: RESPONSE from [GET] /io/classes/Object: {
13:56:55 web.1   |    "response": {
13:56:55 web.1   |      "results": []
13:56:55 web.1   |    }
13:56:55 web.1   |  } results=[]
13:56:55 web.1   |  info: Ran cloud function ------ for user QD8aQ2KCc4 with:
13:56:55 web.1   |    Input: {}
13:56:55 web.1   |    Result: [] functionName=------
13:56:55 web.1   |  verbose: RESPONSE from [POST] /io/functions/------: {
13:56:55 web.1   |    "response": {
13:56:55 web.1   |      "result": []
13:56:55 web.1   |    }
13:56:55 web.1   |  } result=[]


@flovilmart
Copy link
Contributor

You’re matching the zip code or the offers?

@jeremypiednoel
Copy link
Contributor Author

offers is my array of Object1 in my DB and i try to match it with another array

@jeremypiednoel
Copy link
Contributor Author

I tried

  • containedIn which match at least one item from offers in my array
  • containsAll which try to match all items from my array in offers

but none are matching all my offers in array

@flovilmart flovilmart reopened this May 14, 2018
flovilmart added a commit that referenced this issue May 14, 2018
@flovilmart
Copy link
Contributor

I added a test that should reproduce your issue but it doesn't seem to fail:

#4764

Can you have a look and check whether this is what you were looking for?

@jeremypiednoel
Copy link
Contributor Author

thanks for the test @flovilmart to recreate the case you should request an All with more objects.

so that would be

"requests": [
    {
      "method": "POST",
      "body": {
        "objects": [
          {
            "__type": "Pointer",
            "className": "Object",
            "objectId": "1OtAIeUHLR"
          },
          {
            "__type": "Pointer",
            "className": "Object",
            "objectId": "spsHOyOE5w"
          }
        ]
      },
      "path": "/1/classes/Parent"
    },
    {
      "method": "POST",
      "body": {
        "objects": [
          {
            "__type": "Pointer",
            "className": "Object",
            "objectId": "z16G0O6abX"
          }
        ]
      },
      "path": "/1/classes/Parent"
    },
    {
      "method": "POST",
      "body": {
        "objects": []
      },
      "path": "/1/classes/Parent"
    }
  ]

with query

"where": {
    "objects": {
      "$in": [
        {
          "__type": "Pointer",
          "className": "Object",
          "objectId": "1OtAIeUHLR"
        },
        {
          "__type": "Pointer",
          "className": "Object",
          "objectId": "z16G0O6abX"
        },
        {
        "__type": "Pointer",
        "className": "Object",
        "objectId": "spsHOyOE5w"
        }
      ]
    }
  }

and the output should be object 1 : azbzJDOlYE and object 3 BIEVwKoBj4 because they are both a subarray of the query

@flovilmart
Copy link
Contributor

I added an additional test, and both matches as you would expect. Closing now as I can't reproduce the issue with the provided information.

Feel free to open a PR with the failing tests.

@jeremypiednoel
Copy link
Contributor Author

Thanks @flovilmart

here is the case to reproduce

const objects = [1,2,3,4,5,6,7,8].map((idx) => {
  const obj = new Parse.Object('Object');
  obj.set('key', idx);
  return obj;
});
let parent;
let parent3;

Parse.Object.saveAll(objects).then(() => {
  parent = new Parse.Object('Parent');
  parent.set('objects', objects.slice(0, 3));
  
  const parent2 = new Parse.Object('Parent');
  parent2.set('objects', [objects[1]]);
  
  parent3 = new Parse.Object('Parent');
  parent3.set('objects', objects.slice(1, 4));
  
  return Parse.Object.saveAll([parent, parent2, parent3]);
}).then(() => {
  const query = new Parse.Query('Parent');
  query.containedIn('objects', objects);
  return query.find();
}).then((result) => {
  expect(result[0].id).not.toBeUndefined();
  expect(result[1].id).not.toBeUndefined();
  expect(result[2].id).not.toBeUndefined();
  expect(result.length).toBe(3);
}).then(done).catch(done.fail);

@jeremypiednoel
Copy link
Contributor Author

so we want to have all parent where objects are contained in objects initial array

@flovilmart
Copy link
Contributor

@jeremypiednoel did you test your case because it works as expected.

@jeremypiednoel
Copy link
Contributor Author

Indeed it's working !
That's very weird I probably have a glitch somewhere else in my code.
Thanks a lot @flovilmart

@flovilmart
Copy link
Contributor

No problem! Good luck!

@jeremypiednoel
Copy link
Contributor Author

jeremypiednoel commented May 14, 2018

Ok i found why the key is to add a value in a parent

const objects = [1,2,3,4,5,6,7,8].map((idx) => {
  const obj = new Parse.Object('Object');
  obj.set('key', idx);
  return obj;
});
let parent;
let parent3;

Parse.Object.saveAll(objects).then(() => {
  parent = new Parse.Object('Parent');
  parent.set('objects', objects.slice(0, 3));
  
  let shift = objects.shift();
  // parent2 shouldn't be in the result because shift isn't in the initial array anymore 
  const parent2 = new Parse.Object('Parent');
  parent2.set('objects', [objects[1],shift]);
  
  parent3 = new Parse.Object('Parent');
  parent3.set('objects', objects.slice(1, 4));
  
  return Parse.Object.saveAll([parent, parent2, parent3]);
}).then(() => {
  const query = new Parse.Query('Parent');
  query.containedIn('objects', objects);
  return query.find();
}).then((result) => {
  expect(result[0].id).not.toBeUndefined();
  expect(result[1].id).not.toBeUndefined();
  expect(result.length).toBe(2);
}).then(done).catch(done.fail);

@flovilmart
Copy link
Contributor

@jeremypiednoel I just tested it again and with parent2.set('objects', [shift]); you only get 2 results, which make sense, as shift would remove the 1st object and the query is only on the next objects.

Keeping your code as is, object[1] after the shift is still a valid object in the shifted objects and returns the proper values.

Again, Everyting is good on parse-server side, you should have a look at your logic somewhere or write unit tests in your project as it is not impossible to reproduce the issue.

@jeremypiednoel
Copy link
Contributor Author

If you keep
parent2.set('objects', [objects[1],shift]);

the parent2 shouldn't be in the results right ?

@flovilmart
Copy link
Contributor

it should be because

const objects = [0,1,2,3,4,5]
const shift = objects.shift();
// objects = [1,2,3,4,5]
// shift = 0
// objects[1] == 2

@jeremypiednoel
Copy link
Contributor Author

Yes so parent2.objects = [0,2] and objects = [1,2,3,4,5] so parent2.objects is not containIn objects

@flovilmart
Copy link
Contributor

it matches because there's at least one matching, not all matching one in the provided array.

If you're using containsAll, that's a different story.

@jeremypiednoel
Copy link
Contributor Author

jeremypiednoel commented May 14, 2018

But when i use containsAll it makes sure that all objects are in parent.objects
How can i make sure that all parent.objects are in objects

@jeremypiednoel
Copy link
Contributor Author

Because if i follow your logic
This test should success

const objects = [1,2,3,4,5,6,7,8].map((idx) => {
  const obj = new Parse.Object('Object');
  obj.set('key', idx);
  return obj;
});
let parent;
let parent3;

Parse.Object.saveAll(objects).then(() => {
  parent = new Parse.Object('Parent');
  parent.set('objects', objects.slice(0, 3));
  
  let shift = objects.shift();
  // parent2 shouldn't be in the result because shift isn't in the initial array anymore 
  const parent2 = new Parse.Object('Parent');
  parent2.set('objects', [objects[1],shift]);
  
  parent3 = new Parse.Object('Parent');
  parent3.set('objects', objects.slice(1, 4));
  
  return Parse.Object.saveAll([parent, parent2, parent3]);
}).then(() => {
  const query = new Parse.Query('Parent');
  query.containsAll('objects', objects);
  return query.find();
}).then((result) => {
  expect(result[0].id).not.toBeUndefined();
  expect(result[1].id).not.toBeUndefined();
  expect(result.length).toBe(2);
}).then(done).catch(done.fail);

@flovilmart
Copy link
Contributor

No it should not because none of the Parent object contains all passed objects.

@jeremypiednoel
Copy link
Contributor Author

Ok so my question is how can i make sure that i return object where parent.objects are all contains into the initial array.
And not at least one like containedIn does ?

@flovilmart
Copy link
Contributor

That would be the containsAll, as tested in the first test.

@jeremypiednoel
Copy link
Contributor Author

jeremypiednoel commented May 14, 2018

The contains all make sure full objects is in parent.objects
I want the opposite full parent.objects is on objects

@flovilmart
Copy link
Contributor

So there’s no operator to achieve that directly it seems. Besides the ‘equalTo’ operator.

@jeremypiednoel
Copy link
Contributor Author

When i try with the equalTo I have

17:11:40 web.1   |  error: You cannot use [object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object] as a query parameter. code=141, message=You cannot use [object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Objec
17:11:40 web.1   |  >  t],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object] as a query parameter.

@flovilmart
Copy link
Contributor

Which makes sense as equals doesn't support array transformations and even less with pointers.

In any case, all the examples that were implemented today are working and from what I understand, what you're trying to do is either unimplemented or unsupported / undocumented.

If it's an additional feature that you need, feel free to open a PR providing it. If that's an issue as it's documented somewhere in the guides somewhere, feel free to reopen one.

@jeremypiednoel
Copy link
Contributor Author

Thanks i'll make some research in parse source code and mongodb queries to see if i can find or implement it

@flovilmart
Copy link
Contributor

Thanks! Good luck if you need any help, feel free to reach out!

@jeremypiednoel
Copy link
Contributor Author

Hey @flovilmart i finally found how to do it using $nor and $elemMatch.
Do you think i should add the support on parse server and the convenient method on parse JS ?

@flovilmart
Copy link
Contributor

That’s interesting! Do you have an idea of how it would be named etc before you get started? Perhaps with a PR on the docs first so we can discuss the API for JS and REST?

@jeremypiednoel
Copy link
Contributor Author

I would say arrayContainedIn ? what do you think ?

@flovilmart
Copy link
Contributor

It may be confusing with containedIn, isn’t it arrayMatches?

@jeremypiednoel
Copy link
Contributor Author

yes arrayMatches is good !

@jeremypiednoel
Copy link
Contributor Author

I created the PR #4766 to just add the mongo elements

dplewis pushed a commit that referenced this issue May 18, 2018
* Adding elemMatch and nor

* lint

* adding test

* adding edge test

* postgres support

* clean up

* empty test
UnderratedDev pushed a commit to UnderratedDev/parse-server that referenced this issue Mar 21, 2020
…parse-community#4766)

* Adding elemMatch and nor

* lint

* adding test

* adding edge test

* postgres support

* clean up

* empty test
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

No branches or pull requests

2 participants