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

Parse.Query.find() returns Parse.Pointer[] instead of Parse.Object[] #5521

Open
RaschidJFR opened this issue Apr 17, 2019 · 50 comments · Fixed by #7002 · May be fixed by parse-community/Parse-SDK-JS#924
Open

Parse.Query.find() returns Parse.Pointer[] instead of Parse.Object[] #5521

RaschidJFR opened this issue Apr 17, 2019 · 50 comments · Fixed by #7002 · May be fixed by parse-community/Parse-SDK-JS#924
Labels
type:bug Impaired feature or lacking behavior that is likely assumed

Comments

@RaschidJFR
Copy link
Contributor

RaschidJFR commented Apr 17, 2019

Issue Description

When running a simple query on a class from cloud code, the result is an array of pointers to class objects instead of an array of actual objects. When the same query is run on the client it works ok.

This only happens on a specific class (Profile in my case), the same query on any other class returns the expected output.

Steps to reproduce

Call this cloud function function for a certain class (in this case Profile):

Parse.Cloud.define('department:getMembers', request => {
  return new Parse.Query(Profile).find({useMasterKey: true});
});

Expected Results

If I run the same query from the client: new Parse.Query(Profile).find();, what I get is a list of Parse Objects as expected:

{
	"results": [
		{
			"objectId": "SmAkS6KX9F",
			"accreditations": [
				{
					"code": 5,
					"year": 2013
				}
			],
			"awards": [
				{
					"name": "gdsf",
					"awardedBy": "dsgf",
					"year": 2004,
					"details": "hfgdfdhgdfhg"
				}
			],
			// more properties...
		},
		{
			"objectId": "MhOASa4bBa",
			"accreditations": [],
			"awards": [],
			// more properties...
		},
		{
			"objectId": "yIIEz9UIGp",
			"accreditations": [],
			"awards": [],
			// more properties...
		}
	]
}

Actual Outcome

... but when run from cloud code I'm getting just pointers instead of actual objects!:

{
    "result": [
        {
            "__type": "Pointer",
            "className": "Profile",
            "objectId": "SmAkS6KX9F"
        },
        {
            "__type": "Pointer",
            "className": "Profile",
            "objectId": "MhOASa4bBa"
        },
        {
            "__type": "Pointer",
            "className": "Profile",
            "objectId": "yIIEz9UIGp"
        }
    ]
}

Environment Setup

  • Server

    • parse-server version (Be specific! Don't say 'latest'.) : 3.1.1
    • Operating System: Win10
    • Hardware: Intel Core i5-6200U; RAM 12GB
    • Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): Local
  • Database

    • MongoDB version: v4.0.1
    • Storage engine: WiredTiger
    • Hardware: (same as server) Intel Core i5-6200U; RAM 12GB
    • Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): Local

Logs/Trace

info: Ran cloud function department:getMembers for user rhLnSgyBQv with:
  Input: {"department":{"__type":"Pointer","className":"Department","objectId":"nBcxb3juBx"}}
  Result: [{"__type":"Pointer","className":"Profile","objectId":"SmAkS6KX9F"},{"__type":"Pointer","className":"Profile","objectId":"MhOASa4bBa"},{"__type":"Pointer","className":"Profile","objectId":"yIIEz9UIGp"}] functionName=department:getMembers, __type=Pointer, className=Department, objectId=nBcxb3juBx, user=rhLnSgyBQv
verbose: RESPONSE from [POST] /scope/functions/department:getMembers: {
  "response": {
    "result": [
      {
        "__type": "Pointer",
        "className": "Profile",
        "objectId": "SmAkS6KX9F"
      },
      {
        "__type": "Pointer",
        "className": "Profile",
        "objectId": "MhOASa4bBa"
      },
      {
        "__type": "Pointer",
        "className": "Profile",
        "objectId": "yIIEz9UIGp"
      }
    ]
  }
} result=[__type=Pointer, className=Profile, objectId=SmAkS6KX9F, __type=Pointer, className=Profile, objectId=MhOASa4bBa, __type=Pointer, className=Profile, objectId=yIIEz9UIGp]

Some more code

For what it's worth, this is the code for the class Profile.ts.

import Parse from '@parse';
import { User } from '@library/user/user';
// some more imports...

export class Profile extends Parse.Object {

	constructor(attributes?: IProfile) {
		super('Profile', attributes);

		this.accreditations = attributes && attributes.accreditations || [];
		this.awards = attributes && attributes.awards || [];
		this.seniority = attributes && attributes.seniority || [];
		this.degrees = attributes && attributes.degrees || [];
	}

	get user(): User { return this.get('user'); }
	set user(val: User) { this.set('user', val); }

         // some more setters and functions...

	toJSON(): IProfile {
		const json = super.toJSON();
		json.photoUrl = this.photoUrl;
		return json;
	}
}

// IMPORTANT: Register as Parse subclass
Parse.Object.registerSubclass('Profile', Profile);
@dplewis
Copy link
Member

dplewis commented Apr 18, 2019

Thank you for the detailed report. Can you write a failing test?

@acinader
Copy link
Contributor

@RaschidGithub did you ever figure this one out? Interesting. I suspect that it is a misconfiguration on your part. Turning on verbose logging may help in tracking it down what is going on.

If I have read your account properly, this is only happening with one class and not the others. You write: "This only happens on a specific class (Project in my case)", but then use 'Profile' everywhere else.

@RaschidJFR
Copy link
Contributor Author

Thanks @acinader , I've edited my description as I miswrote the sentence: it should read "... on a specific class (Profile...".

I've turned on verbose, it's pasted on the issue description. I'm gonna try the test @dplewis suggested and get back to you with the result. I'll just have to figure out how that is done hehe.

@RaschidJFR
Copy link
Contributor Author

Thank you for the detailed report. Can you write a failing test?

@dplewis so I forked the repo (I guess I need to run my local server from the /bin folder inside it), installed Jasmine Test Explorer and did npm run coverage... I'm not familiar with this tests, so what's the outcome I should post here?

@dplewis
Copy link
Member

dplewis commented Apr 25, 2019

If you got the tests running and passing, write a new test to reproduce your issue.

If your new test fails then create a Pull Request.

https://github.com/parse-community/parse-server/blob/master/CONTRIBUTING.md

@davimacedo
Copy link
Member

@RaschidGithub I've just noticed in your logs that you are sending a parameter called "department" but you are not using this parameter in the function you included in your issue description. I am just wondering if you really included the whole code of the function. Can you please double check?

@RaschidJFR
Copy link
Contributor Author

@davimacedo Yes, the cloud function was supposed to do some more complex querying, but as it wasn't working I reduced it to just one line ( return new Parse.Query(Profile).find({useMasterKey: true});) to get rid of any other potential errors. So the that's my complete code, I'm sending parameters that are not being used at all for now.

@davimacedo
Copy link
Member

Can you try the code below to see what happens?

Parse.Cloud.define('department:getMembers', request => {
return new Parse.Query('Profile').find({useMasterKey: true});
});

The idea is to not use the Profile class just to make sure that the problem is in the API and not in the code.

@dplewis
Copy link
Member

dplewis commented May 2, 2019

@RaschidGithub I wrote a test case and it passed. I don't think this is an API issue.

@RaschidJFR
Copy link
Contributor Author

Can you try the code below to see what happens?

Parse.Cloud.define('department:getMembers', request => {
return new Parse.Query('Profile').find({useMasterKey: true});
});

The idea is to not use the Profile class just to make sure that the problem is in the API and not in the code.

Exact same result @davimacedo ... this is so weird. I even tried with and without the masterkey just because and still failing.

I'm starting to thing I may have broke something on maybe on the mongodb, the strange thing is that this happens also on the remove server on [back4app](https://back4app.com].

Thanks for the case @dplewis . I know, this code is too simplt to cause such an issue. I'm gonna start testing my git history and see If I can when it stopped working.

@RaschidJFR
Copy link
Contributor Author

RaschidJFR commented May 3, 2019

I'm noticing something in the logs that I left out before: the server fetches the complete Objects
but it converts them to Pointers just before returning from the async request:

// Get Members
Parse.Cloud.define('department:getMembers', async (request) => {
	const profiles = await new Parse.Query('Profile').first();
	console.log('\n=========== I FETCHED THIS ================\n%o\n==============================\n', JSON.stringify(profiles));

	return profiles;  // <-- Here they get turned into Parse.Pointer
});
// LOG
verbose: REQUEST for [POST] /scope/functions/department:getMembers: {} method=POST, url=/scope/functions/department:getMembers, x-parse-application-id=***********, x-parse-rest-api-key=*******, content-type=application/json, user-agent=PostmanRuntime/7.11.0, accept=*/*, cache-control=no-cache, postman-token=53ee8188-3355-4e4f-85cc-45ccb4e1f7d6, host=localhost:1337, accept-encoding=gzip, deflate, content-length=0, connection=keep-alive,
verbose: REQUEST for [GET] /scope/classes/Profile: {
  "where": {},
  "limit": 1
} method=GET, url=/scope/classes/Profile, user-agent=node-XMLHttpRequest, Parse/js2.1.0 (NodeJS 10.14.1), accept=*/*, content-type=text/plain, host=localhost:1337, content-length=208, connection=close, , limit=1
verbose: RESPONSE from [GET] /scope/classes/Profile: {
  "response": {
    "results": [
      {
        "objectId": "aTwDsUAhUi",
        "accreditations": [],
        "awards": [],
        "seniority": [],
        "degrees": [],
        "firstName": "Raschid",
        "lastName": "Rafaelly",
        "user": {
          "__type": "Pointer",
          "className": "_User",
          "objectId": "4IgZmPWknB"
        },
        "createdAt": "2019-04-19T00:43:44.270Z",
        "updatedAt": "2019-04-23T00:57:14.654Z",
        "photo": {
          "__type": "File",
          "name": "5ded977b48566e271421ff0ad0912429_RS17239jpg.jpeg",
          "url": "http://localhost:1337/scope/files/oqUavu8VpWVZJ3WeK2RDDTKXbFMDY95bMuCwLvD2/5ded977b48566e271421ff0ad0912429_RS17239jpg.jpeg"
        },
        "ACL": {
          "4IgZmPWknB": {
            "read": true,
            "write": true
          },
          "*": {
            "read": true
          }
        }
      }
    ]
  }
} results=[objectId=aTwDsUAhUi, accreditations=[], awards=[], seniority=[], degrees=[], firstName=Raschid, lastName=Rafaelly, __type=Pointer, className=_User, objectId=4IgZmPWknB, createdAt=2019-04-19T00:43:44.270Z, updatedAt=2019-04-23T00:57:14.654Z, __type=File, name=5ded977b48566e271421ff0ad0912429_RS17239jpg.jpeg, url=http://localhost:1337/scope/files/oqUavu8VpWVZJ3WeK2RDDTKXbFMDY95bMuCwLvD2/5ded977b48566e271421ff0ad0912429_RS17239jpg.jpeg, read=true, write=true, read=true]

=========== I FETCHED THIS ================
'{"accreditations":[],"awards":[],"seniority":[],"degrees":[],"firstName":"Raschid","lastName":"Rafaelly","user":{"__type":"Pointer","className":"_User","objectId":"4IgZmPWknB"},"createdAt":"2019-04-19T00:43:44.270Z","updatedAt":"2019-04-23T00:57:14.654Z","photo":{"__type":"File","name":"5ded977b48566e271421ff0ad0912429_RS17239jpg.jpeg","url":"http://localhost:1337/scope/files/oqUavu8VpWVZJ3WeK2RDDTKXbFMDY95bMuCwLvD2/5ded977b48566e271421ff0ad0912429_RS17239jpg.jpeg"},"ACL":{"4IgZmPWknB":{"read":true,"write":true},"*":{"read":true}},"objectId":"aTwDsUAhUi","photoUrl":"http://localhost:1337/scope/files/oqUavu8VpWVZJ3WeK2RDDTKXbFMDY95bMuCwLvD2/5ded977b48566e271421ff0ad0912429_RS17239jpg.jpeg"}'
==============================

info: Ran cloud function department:getMembers for user undefined with:
  Input: {}
  Result: {"__type":"Pointer","className":"Profile","objectId":"aTwDsUAhUi"} functionName=department:getMembers, , user=undefined
verbose: RESPONSE from [POST] /scope/functions/department:getMembers: {
  "response": {
    "result": {
      "__type": "Pointer",
      "className": "Profile",
      "objectId": "aTwDsUAhUi"
    }
  }
} __type=Pointer, className=Profile, objectId=aTwDsUAhUi

@RaschidJFR
Copy link
Contributor Author

RaschidJFR commented May 3, 2019

I'm following your lead @davimacedo and it turns out that the request works fine when I don't register the subclass Profile.

// Parse.Object.registerSubclass('Profile', Profile); // <-- When I comment this line it all (sort of) works fine

@dplewis
Copy link
Member

dplewis commented May 3, 2019

Does the test case I wrote match the issue you are having? If not please write a test case as I have mentioned before.

When in doubt you can debug the code base.

Cloud code router
Cloud code triggers

@RaschidJFR
Copy link
Contributor Author

RaschidJFR commented May 3, 2019

Ok guys, so I've narrowede the problem and I'm able to give the instructions to reproduce the bug (should I update the main issue description above?).

  1. Just run this code as your main.ts/main.js
  2. Call the cloud function department:getmembers

@dplewis I'm not sure how to write a test case (I have null experience on it), but may be it's something like this (If you run this code you'll get the issue as described).

It seems that the line to init the array prop to [] is causing the issue:

// main.ts

// Create class
class MyClass extends Parse.Object {
    constructor(attributes?) {
	super('MyClass', attributes);
	this.myArrayProp = [];		// <-- This line is causing the issue!!
    }

	get myArrayProp(): any[] { return this.get('myArrayProp'); }
	set myArrayProp(val: any[]) { this.set('myArrayProp', val); }
}

// Create and save a test object
new MyClass().save({
	myArrayProp: [{ id: 1 }, { id: 2 }]
});

// Register as subclass
Parse.Object.registerSubclass('MyClass', MyClass);

// Define cloud function
Parse.Cloud.define('department:getMembers', () => {
	return new Parse.Query(MyClass).find();
});

@dplewis
Copy link
Member

dplewis commented May 3, 2019

Check out the Contributing Guide

Add your code here

PS: This isn't a typescript project

@davimacedo
Copy link
Member

@RaschidGithub if you remove the line you mentioned, does it work? Why do you need this line? I think it can be just removed.

@RaschidJFR
Copy link
Contributor Author

RaschidJFR commented May 9, 2019

Yes, it does work if I remove the mentioned line. It's not a critical line, its porpuse is to initialize the value of the property myArrayProp.

For now I'm using this workaround, but I don't think this is how it's supposed to work:

class MyClass extends Parse.Object {
	constructor(attributes?) {
		super('MyClass', attributes);
		//this.myArrayProp = [];    // <-- This line is causing the issue!!
	}

	get myArrayProp(): any[] {
		const arr = this.get('myArrayProp');
		if (!arr) this.set('myArrayProp', []);	// Init if null
		return this.get('myArrayProp');
	}
	set myArrayProp(val: any[]) { this.set('myArrayProp', val); }
}

I hope to get the time this weekend to put my hands on that Jasmine test.

@mrclay
Copy link
Contributor

mrclay commented May 15, 2019

I'm on 2.x and also noticed initializer code in constructors caused this same issue. We have a large team and I put in every class constructor: // WARNING: Do not add logic to Parse.Object constructors

@RaschidJFR
Copy link
Contributor Author

Hey @dplewis so I finally wrote this test case in ParseCloudTest.js and it fails:

 it('run function on subclass', async (done) => {
    const defaultObject = new MySubclass();
    await defaultObject.save();

    let results = await Parse.Cloud.run('QuerySubclassObjects');
    assert.notEqual(results.length, 0);

    let fetchedObject = results.find(item => item.objectId == defaultObject.objectId);
    expect(fetchedObject.get('mySimpleProp')).toBe('foo');
    expect(fetchedObject.get('myArrayProp')).toContain(1);

    const modifiedObject = new MySubclass();
    await modifiedObject.save({
      mySimpleProp: 'bar',
      myArrayProp: [7, 8, 9]
    });
    results = await Parse.Cloud.run('QuerySubclassObjects');
    assert.notEqual(results.length, 0);

    fetchedObject = results.find(item => item.objectId == modifiedObject.objectId);
    expect(fetchedObject.get('mySimpleProp')).toBe('bar');    // <-- Fails !
    expect(fetchedObject.get('myArrayProp')).toContain(9);    // <-- Fails !
    done();
  });

Please take a look and let me know what's next.

@TomWFox
Copy link
Contributor

TomWFox commented Jul 6, 2019

@dplewis @davimacedo wondering if this has been missed?

@dplewis
Copy link
Member

dplewis commented Jul 6, 2019

@TomWFox I can look into it. It hasn't been addressed

@dplewis
Copy link
Member

dplewis commented Jul 7, 2019

@RaschidJFR Thanks for the test case and sorry for the late reply. I think i figured out why your test was failing. Parse.Object.fromJSON() always creates a new instance but your Custom Subclass creates pendingOps which override whatever json that gets passed.

pointers to class objects instead of an array of actual objects.

Can you write a test for your original issue or are you still experiencing it?

@RaschidJFR
Copy link
Contributor Author

Yeah, I'm still having the problem. That's the test case for my original issue. Need me to write a different test case?

@dplewis
Copy link
Member

dplewis commented Jul 10, 2019

A test case for the array of pointers issue would be great. The test you already wrote doesn't cover that as you are using fetchedObject.get('mySimpleProp') which wouldn't work on json pointers but actual Parse.Object.

@RaschidJFR
Copy link
Contributor Author

Sorry for my late reply. You're right, I had written that part before but I may have discarded it. I'll check it again and share a test.

RaschidJFR added a commit to RaschidJFR/parse-server that referenced this issue Jul 17, 2019
@RaschidJFR
Copy link
Contributor Author

@dplewis So wrote a more robust test case. For this, second and third cases fail.

So far I've found this:

  1. If an extended object has initial values for any of its props, it is converted to Parse.Pointer in the reply of Cloud Code, otherwise it is sent as if converted with its normal toJSON() method.
  2. The Pointers in the response get converted to the extended class, but as they where pointers before, they get their initial values for their props. In the server's log you can see that they were actually sent as pointers, but I don't know how to get that part into the Jasmin test.
  3. It does not make any difference is the initied prop is an array or a primitive value.

PS. I'm not sure where this test should go, whether in the server's repo or in the sdk's. For now I put it into CloudeCode.spec.js in the parse-server repo. Let me know where it should actually be.

describe('Retrieve subclass objects', () => {

  it('Retrieve objects with no inited properties', async done => {

    class SubclassObject extends Parse.Object {
      constructor(attributes) {
        super('SubclassObject', attributes);
      }
      get name() { return this.get('name'); }
      set name(val) { this.set('name', val); }
    }

    Parse.Object.registerSubclass('SubclassObject', SubclassObject);

    Parse.Cloud.define('SubclassObject:first', () => {
      return new Parse.Query(SubclassObject).first();
    });

    await new SubclassObject({ name: 'Foo' }).save();
    const result = await Parse.Cloud.run('SubclassObject:first');

    expect(result instanceof SubclassObject).toBeTruthy();
    expect(result.name).toBe('Foo');
    done();
  });

  it('Retrieve objects with simple inited properties', async done => {

    class SubclassSimpleProp extends Parse.Object {
      constructor(attributes) {
        super('SubclassSimpleProp', attributes);
        this.name = 'Initial';
      }
      get name() { return this.get('name'); }
      set name(val) { this.set('name', val); }
    }

    Parse.Object.registerSubclass('SubclassSimpleProp', SubclassSimpleProp);

    Parse.Cloud.define('SubclassSimpleProp:first', () => {
      return new Parse.Query(SubclassSimpleProp).first();
    });

    await new SubclassSimpleProp({ name: 'Foo' }).save();
    const result = await Parse.Cloud.run('SubclassSimpleProp:first');

    expect(result instanceof SubclassSimpleProp).toBeTruthy();
    expect(result.name).toBe('Foo');  // <-Fails!
    done();
  });

  it('Retrieve objects with inited array properties', async done => {
    class SubclassArrayProp extends Parse.Object {
      constructor(attributes) {
        super('SubclassArrayProp', attributes);
        this.myArray = ['some', 'initial', 'values'];
      }
      get myArray() { return this.get('myArray'); }
      set myArray(val) { this.set('myArray', val); }
    }

    Parse.Object.registerSubclass('SubclassArrayProp', SubclassArrayProp);
    Parse.Cloud.define('SubclassArrayProp:first', () => {
      return new Parse.Query(SubclassArrayProp).first();
    });

    await new SubclassArrayProp({ myArray: ['custom', 'value'] }).save();
    const result = await Parse.Cloud.run('SubclassArrayProp:first');

    expect(result instanceof SubclassArrayProp).toBeTruthy();
    expect(result.myArray).toContain('custom');  // <-Fails!
    done();
  });
});

@stale
Copy link

stale bot commented Aug 31, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Aug 31, 2019
@davimacedo davimacedo removed the wontfix label Sep 2, 2019
@RaschidJFR
Copy link
Contributor Author

Yes, I can tackle this. Just give me a starting point to look.

@dplewis
Copy link
Member

dplewis commented Sep 2, 2019

https://github.com/parse-community/Parse-SDK-JS/blob/master/src/ParseObject.js#L1704

In fromJSON creates a new object. Your SubClass creates pendingOps.

The solution is to delete the pending ops when creating a new object.

I’m not sure this will have side effects when using subclasses.

@dplewis
Copy link
Member

dplewis commented Sep 2, 2019

@RaschidJFR Which one of the following makes sense?

  1. Clear pendingOps in fromJSON
class SubclassSimpleProp extends Parse.Object {
  constructor(attributes) {
    super('SubclassSimpleProp', attributes);
    this.name = 'Initial'; // This won't be set since the pendingOps are cleared.
  }
  get name() { return this.get('name'); }
  set name(val) { this.set('name', val); } // creates pending ops
}
  1. If pendingOps exists set the attributes in fromJSON
class SubclassSimpleProp extends Parse.Object {
  constructor(attributes) {
    super('SubclassSimpleProp', attributes);
    this.name = 'Initial'; // This will be set when calling object.name() but what would happen if fromJSON(json) the json object also has a name field should that json object name field get overrided?
  }
  get name() { return this.get('name'); }
  set name(val) { this.set('name', val); }
}

@RaschidJFR
Copy link
Contributor Author

I think the second case makes more sense being the value in fromJSON the lasting value.

@dplewis
Copy link
Member

dplewis commented Sep 3, 2019

Feel free to submit a PR on the JS SDK.

RaschidJFR added a commit to RaschidJFR/Parse-SDK-JS that referenced this issue Sep 15, 2019
* Test failing with error "Expected 'default' to be 'foo'."
* `jasmine.DEFAULT_TIMEOUT_INTERVAL` was increased to `10000` because the first cases often failed when starting integration testing.

Related to issue parse-community/parse-server#5521
@RaschidJFR
Copy link
Contributor Author

I created a PR. It is failing some tests but you'll get the general idea. I'll continue on it next week.

@thunderwin
Copy link

Guys. how to solve this problem??? I have to install the newest SDK still won't work.. My find() method return a object not a array.. wired..

@RaschidJFR
Copy link
Contributor Author

@thunderwin can you share a bit of code?

@suathh
Copy link

suathh commented Mar 19, 2020

I am having same issue with custom class

Parse.Cloud.afterFind('ProfileQuestion', async (req) => {
  let objects = req.objects;
  let relationArray = [];
  objects.forEach(obj => {
    if (obj.get('data_type') == 'relation') {
      relationArray.push(obj);
    }
  });
  let promiseArray = [];
  relationArray.forEach(relation => {
    let query = new Parse.Query(relation.get('target_class'));
    query.includeAll();
    promiseArray.push(query.find());
  });
  let results = await Promise.all(promiseArray);
  for (let i = 0; i < objects.length; i++) {
    const nObj = objects[i];
    let res = results[i];
    nObj.set('choices', res);
  }
  return objects;
});

This returns an array of pointers.

@ChineOnw
Copy link

Any updates on this? I am encountering the same error.

@RaschidJFR
Copy link
Contributor Author

@ChineOnw @suathh The problem seems to pop up when the extending class has initial values (check this comment). is this your case? If so, maybe you get around this by removing the initial values. Try it and let me know how that goes.

@suathh
Copy link

suathh commented Jun 1, 2020

It has been a long time I don't remember if I had an initial value. How can I check if my class has an initial value ?

@RaschidJFR
Copy link
Contributor Author

Something like this would be setting initial values:

  class SubclassArrayProp extends Parse.Object {
      constructor(attributes) {
        super('SubclassArrayProp', attributes);
        this.myArray = ['some', 'initial', 'values'];  // <- like this
      }
      get myArray() { return this.get('myArray'); }
      set myArray(val) { this.set('myArray', val); }
    }

@suathh
Copy link

suathh commented Jul 9, 2020

I did not check this yet but one of my old parse instances doing same thing. In my 2.6.5 V server there is no default value option when I am adding a new column from parse-dashboard but still getting pointers when I query a relation.

@cbaker6
Copy link
Contributor

cbaker6 commented Nov 10, 2020

@dplewis @davimacedo @mtrezza I'm going to pick this one up as I "think" ran into a similar issue while testing Parse-Swift and I think I know how to replicate it.

  1. The issue occurs on mongo and postgres
  2. To replicate, I create a ParseObject that that contains a Pointer field, and a Pointer array field.
  3. The following scenarios work or are broken
  • includeAll (broken) - will only return all pointers
  • include["PointerFieldKey"] (works) - will return the actual object
  • include["PointerArrayFieldKey"](works) - will return the array of actual objects
  • include["PointerFieldKey", "PointerArrayFieldKey"](broken) - will return the actual object of PointerFieldKey, but return pointers to PointerArrayFieldKey

I'll open a PR later today with a failing test case and hopefully a fix (I haven't investigated where the problem is yet). If you have any leads, let me know

@cbaker6
Copy link
Contributor

cbaker6 commented Nov 10, 2020

Small update... I think the issue may have something to do with this comment:

// The format for this.include is not the same as the format for the
// include option - it's the paths we should include, in order,
// stored as arrays, taking into account that we need to include foo
// before including foo.bar. Also it should dedupe.
// For example, passing an arg of include=foo.bar,foo.baz could lead to
// this.include = [['foo'], ['foo', 'baz'], ['foo', 'bar']]
this.include = [];
// If we have keys, we probably want to force some includes (n-1 level)
// See issue: https://github.com/parse-community/parse-server/issues/3185
if (Object.prototype.hasOwnProperty.call(restOptions, 'keys')) {
const keysForInclude = restOptions.keys
.split(',')
.filter(key => {
// At least 2 components
return key.split('.').length > 1;
})
.map(key => {
// Slice the last component (a.b.c -> a.b)
// Otherwise we'll include one level too much.
return key.slice(0, key.lastIndexOf('.'));
})
.join(',');
// Concat the possibly present include string with the one from the keys
// Dedup / sorting is handle in 'include' case.
if (keysForInclude.length > 0) {
if (!restOptions.include || restOptions.include.length == 0) {
restOptions.include = keysForInclude;
} else {
restOptions.include += ',' + keysForInclude;
}
}
}

As it led me to switch the order (to alphabetical, wasn't alphabetical in my example above) of the names of the fields which resulted in the following change my last scenario above:

  • include["PointerFieldKey", "PointerArrayFieldKey"](broken) - will return the actual object of PointerFieldKey, but return pointers to PointerArrayFieldKey acts like includeAll will only return all pointers

@dplewis
Copy link
Member

dplewis commented Nov 11, 2020

Reopening as the original issue hasn't been addresssed.

@dplewis dplewis reopened this Nov 11, 2020
@NuclleaR
Copy link

NuclleaR commented Dec 18, 2020

Hello!
I donno if it's related issue.
My case:

async function getParentCategory(parent) {
  let objectId;

  if (typeof parent === 'string') {
    objectId = parent;
  } else {
    objectId = parent.id;
  }

  const query = new Parse.Query('ParentCategory');
  query.equalTo('objectId', objectId);

  const [object] = await query.find();

  return object;
}

Parse.Cloud.afterLiveQueryEvent('Categories', async function(request) {
  const parent = await getParentCategory(request.object.get('parentId'));
  console.log('>>> parent ', object);
  request.object.set('parent', parent);
  // Why Pointer?
  console.log('>>> object ', request.object);
});

First log parent is ParseObject
Second log parent is Pointer

I used Flutter sdk and in App I receive only Pointer with just id but not whole parent object. What I did wrong?
I read all thread and can't figure out how to fix it

@RaschidJFR
Copy link
Contributor Author

@NuclleaR It may or it may not be related. I think there's some info missing to have a better diagnose, like the implementation of the involved classes and how you are registering subclasses. Also console output would help.

You should write a test case or a minimal code block in a blank project to test if the problem can be reproduced and is in fact related to this issue. Go ahead and share it here if you find it so.

@yozef
Copy link

yozef commented Mar 3, 2021

I'm having the same issue. I have Objects that have relations to one or more _User(s). Take the example below, Car class Object has an owner property that is pointing to a User. I see them linked in the Dashboard, however doing a simple GET Query via cURL (REST API):

## cURL
curl "https://PARSE_SERVER_API_URL/1/classes/Car" \
     -H 'X-Parse-Application-Id: XXXYYYYYZZZZ' \
     -H 'X-Parse-Rest-Api-Key: XXXYYYYYZZZZ' \
     --data-urlencode "include=owner"

Note: This is taken straight out of the REST API documentation. All Objects stored on server have been made via REST API, and verified in Dashboard.

This API call, returns me all Cars Objects, with the owner property showing me just: __type & className without the full User object.

This can be tested on any running Parse Server v2.3.3 up to v3.6.0 where you have a custom Object with relations pointing to another Object. Adding --data-urlencode "include=propertyOfRelation" will not return you the full relational object.

@dplewis
Copy link
Member

dplewis commented Mar 3, 2021

@yozef I think you have a different issue than the op. If you are trying to query a relation field try $relatedTo.

curl -X GET \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -G \
  --data-urlencode 'where={"$relatedTo":{"object":{"__type":"Pointer","className":"Post","objectId":"8TOXdXf3tz"},"key":"likes"}}' \
  https://YOUR.PARSE-SERVER.HERE/parse/users

@dblythy
Copy link
Member

dblythy commented Apr 1, 2021

@NuclleaR I can replicate your issue and am working on a fix

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:bug Impaired feature or lacking behavior that is likely assumed
Projects
None yet