Skip to content

Conversation

swittk
Copy link

@swittk swittk commented Oct 11, 2025

Pull Request

Issue

Closes: #8500

Approach

This PR addresses issue #8500 where relation fields are never excluded from the returned objects when excludeKeys or specific keys are specified. This is due to the code of both mongoObjectToParseObject and postgresObjectToParseObject internally always spreading the "Relation" keys to the returned object as pointers.
The approach taken in this PR is we check if specific keys are specified, and if so, we filter out the Relation fields outside. (A different approach would require passing in those specific keys into mongoObjectToParseObject and postgresObjectToParseObject so they can skip internally too).
A minimal edit to GraphQL was necessary too due to previous behaviour not traversing edge.node properly for nested fields, meaning it never addressed nested fields properly, which with this PR would fail. This (hopefully) corrects the behaviour to as intended.

Tasks

  • Add tests

Summary by CodeRabbit

  • Bug Fixes

    • Relation field selection now respects requested keys: only included relations are returned, excluded relations are omitted, and default behavior is preserved.
    • GraphQL field normalization improved to handle nested edge.node paths for accurate projections.
    • Consistent filtering of non-selected relation fields across storage backends for cleaner responses.
  • Tests

    • Added tests validating inclusion, exclusion, and default behavior for relation field selection.

Copy link

parse-github-assistant bot commented Oct 11, 2025

🚀 Thanks for opening this pull request!

Copy link

coderabbitai bot commented Oct 11, 2025

📝 Walkthrough

Walkthrough

Adds a test for keys/excludeKeys behavior on relation fields. Updates Mongo and Postgres storage adapters to prune relation fields not requested by keys/excludeKeys. Adjusts GraphQL field normalization to strip nested "edges.node" segments. Minor formatting edits in GraphQL utilities and types.

Changes

Cohort / File(s) Summary
Tests: Parse.Query relation keys handling
spec/ParseQuery.spec.js
Adds a test verifying inclusion/exclusion and default behavior of relation fields when using keys and excludeKeys in Parse.Query.
Storage: Mongo adapter relation pruning
src/Adapters/Storage/Mongo/MongoStorageAdapter.js
After mapping Mongo results to Parse objects, when keys are provided, removes Relation fields not present in the requested keys before returning results.
Storage: Postgres adapter relation pruning
src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Captures a snapshot of requested keys (selectedKeys) and, after converting results to Parse objects, prunes relation fields that are not included in selectedKeys.
GraphQL: query field normalization
src/GraphQL/loaders/parseClassQueries.js
Field normalization in the Find flow now strips both leading edges.node. prefixes and any .edges.node occurrences within field paths to produce correct root keys.
GraphQL: type field selection mapping
src/GraphQL/loaders/parseClassTypes.js
Extends relation field projection logic to strip leading and nested edges.node segments; minor reflow of _User.authDataResponse field formatting.
GraphQL: utils formatting
src/GraphQL/parseGraphQLUtils.js
Minor formatting change in extractKeysAndInclude (no behavioral change).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API as Parse Server API
  participant Adapter as StorageAdapter (Mongo/Postgres)
  participant DB as Database

  Client->>API: Query (where, keys/excludeKeys)
  API->>Adapter: find(className, query, { keys/excludeKeys })
  Adapter->>DB: Execute DB query
  DB-->>Adapter: Results (raw)
  Adapter->>Adapter: Map DB objects -> Parse objects
  rect rgb(235,245,255)
    note right of Adapter: Post-map filtering (new)
    Adapter->>Adapter: If keys provided, remove Relation fields not in keys\nIf excludeKeys provided, remove excluded Relation fields
  end
  Adapter-->>API: Filtered Parse objects
  API-->>Client: Response
Loading
sequenceDiagram
  autonumber
  participant GQL as GraphQL Layer
  participant Loader as parseClassQueries / parseClassTypes
  participant API as Parse Server API

  GQL->>Loader: Selected fields (may include edges.node)
  Loader->>Loader: Normalize fields: strip leading "edges.node." and any ".edges.node" segments
  Loader-->>API: keys/include projection
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • mtrezza

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The changes implement the core requirement from issue #8500 by filtering out relation fields when specific keys or excludeKeys are provided in both Mongo and Postgres adapters, add corresponding tests to validate this behavior, and include necessary GraphQL adjustments to handle nested edge.node paths.
Out of Scope Changes Check ✅ Passed All functional modifications directly relate to filtering relation fields in storage adapters and supporting GraphQL nested fields as described in the PR objectives, and the only additional change is a harmless formatting update in parseGraphQLUtils.js that does not affect functionality.
Description Check ✅ Passed The pull request description follows the repository template by including the required sections (“Pull Request,” “Issue,” “Approach,” and “Tasks”), correctly links to issue #8500, describes the changes made and lists the completed test task.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Title Check ✅ Passed The title clearly summarizes the main change by stating that relation columns will now respect the keys and excludeKeys parameters and no longer always appear in query results, directly reflecting the core fix implemented in the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Oct 11, 2025

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@swittk swittk changed the title Potential Fix for issue #8500; When querying a class that holds a Relation column the Relation column is always present in the query result regardless of keys/excludeKeys query parameter fix: Potential Fix for issue #8500; When querying a class that holds a Relation column the Relation column is always present in the query result regardless of keys/excludeKeys query parameter Oct 11, 2025
@swittk swittk changed the title fix: Potential Fix for issue #8500; When querying a class that holds a Relation column the Relation column is always present in the query result regardless of keys/excludeKeys query parameter fix: issue #8500; Relation column is always present in the query result regardless of keys/excludeKeys query parameter Oct 11, 2025
Copy link

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title fix: issue #8500; Relation column is always present in the query result regardless of keys/excludeKeys query parameter fix: Issue #8500; Relation column is always present in the query result regardless of keys/excludeKeys query parameter Oct 11, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Adapters/Storage/Mongo/MongoStorageAdapter.js (1)

667-690: LGTM! Proper guard conditions and filtering logic.

The implementation correctly filters non-requested Relation fields with proper guard conditions (Array.isArray(keys) && keys.length > 0). This approach is safer than the Postgres adapter version and should be used as the reference pattern.

Optional: Consider extracting shared logic.

The filtering logic is duplicated between Mongo and Postgres adapters. Consider extracting it into a shared utility function to reduce duplication and ensure consistency:

// In a shared utility file
function filterRelationFields(parseObject, schema, requestedKeys) {
  if (!Array.isArray(requestedKeys) || requestedKeys.length === 0) {
    return parseObject;
  }
  
  const keysSet = new Set(requestedKeys);
  Object.keys(schema.fields).forEach(fieldName => {
    if (schema.fields[fieldName].type === 'Relation' && !keysSet.has(fieldName)) {
      delete parseObject[fieldName];
    }
  });
  
  return parseObject;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b606ae and b77a24b.

📒 Files selected for processing (6)
  • spec/ParseQuery.spec.js (1 hunks)
  • src/Adapters/Storage/Mongo/MongoStorageAdapter.js (1 hunks)
  • src/Adapters/Storage/Postgres/PostgresStorageAdapter.js (2 hunks)
  • src/GraphQL/loaders/parseClassQueries.js (1 hunks)
  • src/GraphQL/loaders/parseClassTypes.js (2 hunks)
  • src/GraphQL/parseGraphQLUtils.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/Adapters/Storage/Mongo/MongoStorageAdapter.js (2)
src/Adapters/Storage/Mongo/MongoTransform.js (1)
  • mongoObjectToParseObject (1105-1293)
src/RestQuery.js (3)
  • className (962-962)
  • keys (188-191)
  • keys (971-971)
spec/ParseQuery.spec.js (1)
spec/schemas.spec.js (3)
  • Parse (3-3)
  • request (6-6)
  • masterKeyHeaders (143-147)
src/Adapters/Storage/Postgres/PostgresStorageAdapter.js (1)
src/RestQuery.js (4)
  • keys (188-191)
  • keys (971-971)
  • results (752-752)
  • className (962-962)
🔇 Additional comments (6)
src/GraphQL/parseGraphQLUtils.js (1)

23-25: LGTM! Formatting improvement.

The multi-line formatting enhances readability without changing behavior.

src/GraphQL/loaders/parseClassQueries.js (1)

110-116: LGTM! Comprehensive edge-node segment removal.

The enhanced normalization correctly handles both root-level edges.node. prefixes and nested .edges.node segments within field paths. The explanatory comments clearly document the intent and behavior.

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js (1)

1924-1947: Filtering logic is correct, pending the guard fix above.

The post-processing filter correctly removes non-requested Relation fields when keys are specified. The approach mirrors the MongoStorageAdapter implementation (lines 667-690), ensuring consistent behavior across storage backends.

src/GraphQL/loaders/parseClassTypes.js (2)

354-357: LGTM! Formatting adjustment only.

Minor formatting change to the authDataResponse field declaration with no functional impact.


389-395: LGTM! Consistent field normalization with parseClassQueries.js.

The enhanced normalization logic correctly strips both edges.node. prefixes and nested .edges.node segments, mirroring the approach in parseClassQueries.js (lines 110-116). The explanatory comments document the behavior clearly.

spec/ParseQuery.spec.js (1)

3773-3836: LGTM! Comprehensive test coverage for relation field filtering.

The test thoroughly validates the fix for issue #8500 across four key scenarios:

  1. keys='name' correctly omits the relation field
  2. keys='name,children' correctly includes the relation field
  3. excludeKeys='children' correctly omits the relation field
  4. Default behavior (no keys/excludeKeys) correctly returns the relation field

The test structure is clear, follows existing patterns in the file, and properly uses async/await with the REST API via the request helper.

Comment on lines +113 to +116
// `users.edges.node.username` -> `users.username`). This preserves the
// synthetic relation placeholders that Parse injects while still
// respecting field projections.
.map(field => field.replace('edges.node.', '').replace(/\.edges\.node/g, ''))
Copy link
Member

Choose a reason for hiding this comment

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

issue: i'm not sure to understand what is happening here, because the first replace will strip the edges.node one time, if you try to remove subsequent edges.node like

users.edges.node.aRelation.edges.node.name not sure about the impact and if developers use the "edges.node" inside a object field it can break implementation, i think there is an issue here or wrong approach

What was the issue here ?

Copy link
Author

@swittk swittk Oct 13, 2025

Choose a reason for hiding this comment

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

To be honest I'm not exactly sure; I saw the Relation column logic fix as relatively straightforward, but then when I ran the whole test suite I encountered a single test in GraphQL that was failing (ParseGraphQLServer Auto API Schema Data Types should support deep nested creation) ; did a few searches around and found that code block that stripped out the connections.
As far as I understand, the first filter originally had a single replace call only stripping the leading edges.node. segment, which produced values like aRelation.edges.node.name for nested
connection selections. Because those values still contained edges.node, the
final filter removed them entirely before they reached extractKeysAndInclude.
In other words, the original logic intentionally dropped any field that
contained a second connection layer (I'm not sure why that was the case originally, but anyway..).
The hypothesis I had was that, since I prevented the relation column from surfacing in queries when specific keys to return were specified (as we expect), the original relation fields which were originally always sent out regardless of any conditions then stopped appearing for this specific GraphQL test.

So I tried a fix replacing all explicit consecutive .edges.node (which for typical Parse fields shouldn't exist as such explicitly anyway) and that test passed and didn't think too much of it. I would be happy to know if I was correct in the assumption anyway. I must admit that I don't fully get all of the GraphQL part, but I hope I understood it enough to fix the issue.

Copy link
Member

Choose a reason for hiding this comment

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

I think you need to drop some console.logs on alpha branch to check how this specific GQL test behave, and what is going though the rest call using the GQL api, and then follow the same process on your PR.

You are maybe right something was wrong before, but worked, because keys feature is permissive (like you can select non existant fields)

Keep me updated :)

@mtrezza mtrezza changed the title fix: Issue #8500; Relation column is always present in the query result regardless of keys/excludeKeys query parameter fix: Relation column is always present in the query result regardless of keys/excludeKeys query parameter Oct 14, 2025
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.

Because relationship columns do not accept keysToReturn or excludeKeys

3 participants