Skip to content

Support deeply nested pointers in 'containedIn' queries #7414

@oallouch

Description

@oallouch
Contributor

New Issue Checklist

Issue Description

Querying using "containedIn" in deep nested objects using pointers doesn't seems to work.

Steps to reproduce

See example below.

Actual Outcome

The queries return empty arrays.

Expected Outcome

We expected an array of Parse.Object .

Failing Test Case / Pull Request

  • 🤩 I submitted a PR with a fix and a test case.
    🧐 I submitted a PR with a failing test case.

To reproduce the issue, I added this at the end of ParseQuery.spec.js

  it('deeply nested Pointers (issue #7413)', async function (done) {
    const parent = new Parse.Object('Parent');
    const child1 = new Parse.Object('Child');
    const child2 = new Parse.Object('Child');

    parent.set('children', [
      { child: child1, count: 2 },
      { child: child2, count: 3 },
    ]);
    await parent.save();

    const results = await new Parse.Query('Parent')
      .containedIn('children.child', [child1])
      .find();
    expect(results.length).toBe(1);
    done();
  });

This test passes if I replace the line :
.containedIn('children.child', [child1])
by
.containedIn('children.child.objectId', [child1.id])

Environment

parse-server 4.5.0

Server

  • Parse Server version: 4.5.0
  • Operating system: Windows and debian
  • Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): local Windows and Google App Engine

Database

  • System (MongoDB or Postgres): MongoDB
  • Database version: 4.4.6
  • Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): local and MongoDB Atlas

Client

  • SDK (iOS, Android, JavaScript, PHP, Unity, etc): Javascript
  • SDK version: 3.2

Logs

Activity

mtrezza

mtrezza commented on Jun 3, 2021

@mtrezza
Member

Thanks for reporting.

I think that makes sense. Are you suggesting that when the objectId is omitted, it should be inferred, as a convenience? That would mean children.child becomes a shorthand for children.child.objectId.

Or should stating the children.child.objectId throw an error?

What would currently happen if a custom field is used, e.g. children.child.myField. Is that an invalid query or would Parse Server generate a proper MongoDB query?

oallouch

oallouch commented on Jun 3, 2021

@oallouch
ContributorAuthor

What I suggest is using children.child as a shorthand for children.child.objectId, like it's actually the case with equalTo.
children.child.objectId should also work, like it does today.
I just would like it to work like it does with simple Pointer arrays.

To sum up, the actual state is :
✅ .containedIn('children.child.objectId', [child1.id])
❌ .containedIn('children.child', [child1])
✅.equalTo('children.child.objectId', child1.id)
✅.equalTo('children.child', child1)

The 2 options without objectId are a more natural way for Parse users to query.
"Give me the Parent with this Child"

Many of my developers have faced this issue.
It's part of the little caveats we tell new devs when they enter the company, like the infamous "dirty() always returns true for new objects, even if the property doesn't exist" 😋

mtrezza

mtrezza commented on Jun 3, 2021

@mtrezza
Member

The 2 options without objectId are a more natural way for Parse users to query.

I think you are right. I agree that it should be possible to do .containedIn('children.child', [child1]).

I am less sure about whether we should allow .containedIn('children.child.objectId', [child1.id]).
Because to me that infers that it would be possible to do children.child.myCustomField, which I think it isn't.
That's why I wonder if we have anything comparable to "specifying an object ID optionally" in other places in the SDK.
If this is the only exception, we could think about removing this bit - or is there any use?

oallouch

oallouch commented on Jun 3, 2021

@oallouch
ContributorAuthor

That would be a breaking change for sure, and I don't think being able to use any Pointer subfield is bad.
I like the fact that Parse provides added value without going to much in the way.

mtrezza

mtrezza commented on Jun 3, 2021

@mtrezza
Member

I like the fact that Parse provides added value without going to much in the way.

We are now following a phased deprecation policy to avoid sudden breaking changes. So developers would have time to adapt to a change. We need to maintaining the code base and that means stripping features that are duplicates or provide little value, even if they are breaking changes.

That's why I'm wondering what benefit .containedIn('children.child.objectId', [child1.id]) provides when this PR adds .containedIn('children.child', [child1]).

oallouch

oallouch commented on Jun 3, 2021

@oallouch
ContributorAuthor

Maybe I just have the id.
I can do .containedIn('children.child.objectId', [childId]) without having to retrieve the object from the base, just to use an id I already have.

And it's not only about objectId. Maybe one time, I'd like to get all the Parse.Object documents with an array containing at least a pointer of class Child.
For this, I would use .containedIn('children.child.className', 'Child')

As I said, I love the way parse-server doesn't get in the way. For example, you can create a Parse.Function or use a direct express route (to download pdf file, for instance). That's what make my clients adopt parse-server. You're not blocked.

I'm curious to know the proportion of parse-server projects that have the equivalent of those few line :

function getDatabaseController() {
	const config = Config.get(Parse.applicationId);
	return config.database;
}

async function getMongoCollection(className) {
	const mongoAdapter = getDatabaseController().adapter;
	await mongoAdapter.connect();
	return mongoAdapter.database.collection(className);
}

Btw, we should get a public API for this 😋. Parse can't (and shouldn't) do everything, it already does so much.

mtrezza

mtrezza commented on Jun 3, 2021

@mtrezza
Member

Would you want to open a PR to add the .containedIn('children.child', [child1]) functionality?

oallouch

oallouch commented on Jun 3, 2021

@oallouch
ContributorAuthor

I can try...

mtrezza

mtrezza commented on Jun 3, 2021

@mtrezza
Member

Great, I assume the .equalTo('children.child.objectId', child1.id) should give some hints on how to approach it.

oallouch

oallouch commented on Jun 8, 2021

@oallouch
ContributorAuthor

I did some tests, and I think I understand the problem now.
I thought equalTo only compared objectId, but it also uses __type and className.
I understand now why you called changing children.child to children.child.objectId a "shorthand".

I found the cause. It's actually a bug in MongoTransform's transformConstraint.

This is what it outputs now.

with equalTo (good result) :

mongoWhere: {
'children.child': { __type: 'Pointer', className: 'Child', objectId: 'BoXaGRlIqO' },
_rperm: { '$in': [ null, '', '' ] }
}

with containedIn (bad result) :

mongoWhere: {
'children.child': { '$in': [ 'Child$BoXaGRlIqO' ] },
_rperm: { '$in': [ null, '', '' ] }
}

I'm preparing the PR.

oallouch

oallouch commented on Jun 10, 2021

@oallouch
ContributorAuthor

The PR is #7426

oallouch

oallouch commented on Jun 14, 2021

@oallouch
ContributorAuthor

@mtrezza , tell me if something is missing from the PR

9 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Participants

      @oallouch@mtrezza@RahulLanjewar93@parseplatformorg

      Issue actions

        Support deeply nested pointers in 'containedIn' queries · Issue #7414 · parse-community/parse-server