parse-community / parse-server

Parse Server for Node.js / Express
https://parseplatform.org
Apache License 2.0
20.83k stars 4.77k forks source link

Saving an Undefined value to a ParseObject Field that is undefined already, creates a null pointer field in MongoDb Atlas Document which creates query issues #8639

Open 432player opened 1 year ago

432player commented 1 year ago

New Issue Checklist

Issue Description

Saving an undefined value into an existing ParseObject field, creates a null pointer in the mongoDb Atlas document. For Example: I have a ParseObject "Form" with a field "student" which is a _User object. When I set "undefined" into the student field and then save the Form object it creates a null pointer field which is not detectable through the parse-server but appears as null in mongoDB as such -> _p_student null

The moment this pointer is created with null value, it is impossible to use doesNotExist and exists in a query because it believes the object exists. While in the parse dashboard it appears as undefined, but acts as an existing object when working with exists & doesNotExist in a query.

Steps to reproduce

Easy, just save an undefined value into an object field of an existing ParseObject and save the object. It will seem as if the value is undefined, but don't be fooled. the dashboard even will show you that it's undefined and when using .get('student') it witll be undefined in code. BUT, when you want to do "exists" in a query it will assume it exists, because when going to mongodb atlas you will find a _p_field_name : null and thus disrupting the exists/doesNotExists functionality in a query.

Actual Outcome

var formQuery = new Parse.Query('Form'); formQuery.doesNotExist('student'); formQuery.find({ useMasterKey: true }) .then(function (formObjects) { //The object where I've inserted an undefined value into student will not return in this query })

Expected Outcome

var formQuery = new Parse.Query('Form'); formQuery.doesNotExist('student'); formQuery.find({ useMasterKey: true }) .then(function (formObjects) { //Having the object which I saved undefined into student should still appear here })

Environment

NodeJs

Server

Database

Client

parse-github-assistant[bot] commented 1 year ago

Thanks for opening this issue!

cbaker6 commented 1 year ago

This is already supported on the Parse-Server and is not a bug. For example, issuing the following queries via REST work fine:

// yolo == null
"{\"_method\":\"GET\",\"limit\":100,\"skip\":0,\"where\":{\"yolo\":null}}"
// yolo == null, alternate way
"{\"_method\":\"GET\",\"limit\":100,\"skip\":0,\"where\":{\"yolo\":{\"$eq\":null}}}"
// yolo != null
"{\"_method\":\"GET\",\"limit\":100,\"skip\":0,\"where\":{\"yolo\":{\"$ne\":null}}}"

Actual tests already implemented on the server, below are some of them: https://github.com/parse-community/parse-server/blob/9674d4a2c0a9d0cda112056a6a2b1629931f37a3/spec/ParseQuery.spec.js#L1293-L1309 https://github.com/parse-community/parse-server/blob/9674d4a2c0a9d0cda112056a6a2b1629931f37a3/spec/ParseQuery.spec.js#L108-L126 https://github.com/parse-community/parse-server/blob/9674d4a2c0a9d0cda112056a6a2b1629931f37a3/spec/ParseQuery.spec.js#L3824-L3840

The actual issue reported is that the JS SDK (and other SDK's) doesn't support these type of query constraints directly and requires equalTo and notEqualTo null instead (note that you can still run into https://github.com/parse-community/Parse-SDK-JS/issues/1372 on the JS SDK). The exists and doesNotExist query constraints work correctly, the JS SDK needs to add two additional constraints to support the REST commands I posted. Currently, the Swift SDK is the only Parse SDK that I know of to support the constraints (isNull and isNotNull).

Note that the differences between (isNull, isNotNull) and (doesNotExist, exists) is only relevant when using MongoDB. For Postgres, using either results in the same outcome respectively, see https://github.com/parse-community/Parse-Swift/pull/308 for more info. Here's how the implementations look in the Swift SDK:

/**
 Add a constraint that requires that a key is equal to **null** or **undefined**.
 - parameter key: The key that the value is stored in.
 - returns: The same instance of `QueryConstraint` as the receiver.
 */
public func isNull (key: String) -> QueryConstraint {
    QueryConstraint(key: key, isNull: true)
}

/**
 Add a constraint that requires that a key is not equal to **null** or **undefined**.
 - parameter key: The key that the value is stored in.
 - returns: The same instance of `QueryConstraint` as the receiver.
 */
public func isNotNull (key: String) -> QueryConstraint {
    QueryConstraint(key: key, comparator: .notEqualTo, isNull: true)
}

/**
  Add a constraint that requires a particular key to not be equal to **undefined**.
  - parameter key: The key that should exist.
  - returns: The resulting `QueryConstraint`.
 */
public func exists(key: String) -> QueryConstraint {
    .init(key: key, value: true, comparator: .exists)
}

/**
  Add a constraint that requires a key to be equal to **undefined**.
  - parameter key: The key that should not exist.
  - returns: The resulting `QueryConstraint`.
 */
public func doesNotExist(key: String) -> QueryConstraint {
    .init(key: key, value: false, comparator: .exists)
}

Example use cases are in Swift Playgrounds: https://github.com/parse-community/Parse-Swift/blob/3d4bb13acd7496a49b259e541928ad493219d363/ParseSwift.playground/Pages/13%20-%20Operations.xcplaygroundpage/Contents.swift#L92-L135