Skip to content

fix: Relation query with limit and descending order under-returns when a related object was deleted#10541

Open
dblythy wants to merge 2 commits into
parse-community:alphafrom
dblythy:fix/relation-limit-dangling-pointer-9600
Open

fix: Relation query with limit and descending order under-returns when a related object was deleted#10541
dblythy wants to merge 2 commits into
parse-community:alphafrom
dblythy:fix/relation-limit-dangling-pointer-9600

Conversation

@dblythy

@dblythy dblythy commented Jul 4, 2026

Copy link
Copy Markdown
Member

Closes #9600

Summary by CodeRabbit

  • New Features

    • Added automatic indexing for relation join tables at startup, with a new setting to turn it off if needed.
    • Improved support for relation handling across databases, including safer startup checks for missing indexes.
  • Bug Fixes

    • Fixed relation queries so deleted related records no longer cause undercounted results.
    • Improved consistency when relation join-table indexes are created or backfilled, including repeated runs.
  • Documentation

    • Documented the new database option for join-table index creation.

@parse-github-assistant

Copy link
Copy Markdown

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR fixes relation query undercounting by removing skip/limit/sort pushdown in join-table reads, and adds indexing on relation join tables (owningId/relatedId) in both Mongo and Postgres adapters, with startup backfilling controlled by a new createIndexJoinTables option.

Changes

Relation Query Fix and Join-Table Indexing

Layer / File(s) Summary
Fix relatedIds pushdown causing undercount
src/Controllers/DatabaseController.js
relatedIds drops queryOptions and always fetches full join-table results; $relatedTo reduction updated to match.
Mongo adapter join-table indexes
src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Removes canSortOnJoinTables, adds config fields, creates two-direction relation join-table indexes, and adds ensureJoinTableIndexes for backfilling.
Postgres adapter join-table indexes
src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Adds hashed index naming, creates owningId/relatedId indexes on table/field creation, removes canSortOnJoinTables, and adds ensureJoinTableIndexes using concurrent index creation.
Interface cleanup and new option wiring
src/Adapters/Storage/StorageAdapter.js, src/Controllers/DatabaseController.js, src/Options/Definitions.js, src/Options/docs.js, src/Options/index.js, src/defaults.js
Removes canSortOnJoinTables from the interface, adds createIndexJoinTables option (default true) across definitions/docs/index/defaults, and wires startup join-table index enforcement in performInitialization.
Regression tests
spec/ParseRelation.spec.js
Adds tests for correct relation counts after deletion, presence of two-direction join-table indexes, and idempotent index backfilling.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: mtrezza, Moumouls

🚥 Pre-merge checks | ✅ 4 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description only says Closes #9600`` and omits the required Issue, Approach, and Tasks sections from the template. Add the template sections for Issue, Approach, and Tasks, and mark the relevant checklist items with the actual changes made.
Out of Scope Changes check ⚠️ Warning The join-table index creation, new database option, and startup backfilling go beyond the bug fix required by #9600. Move the indexing and startup backfill work to a separate PR or link it to a dedicated performance issue.
Engage In Review Feedback ❓ Inconclusive Repo shows issue-linked code changes, but no review thread/comments are available to verify prior feedback engagement. Provide the PR review conversation or comment history showing the feedback was discussed before being resolved, or that the reviewer retracted it.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the required fix: prefix and accurately summarizes the relation query bug being fixed.
Linked Issues check ✅ Passed The changes address #9600 by returning all related IDs before filtering deleted objects, which prevents under-returning.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Security Check ✅ Passed No security issues found: the new SQL/Mongo index creation is parameterized, class/field names are validated, and startup backfill only touches schema metadata.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.1)
src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

File contains syntax errors that prevent linting: Line 11: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 26: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 296: interface are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 302: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 894: classes can only implement interfaces in TypeScript files; Line 895: return types can only be used in TypeScript files; Line 898: return types can only be used in TypeScript files; Line 899: return types can only be used in TypeScript files; Line 900: return types can only be used in TypeScript files; Line 901: return types can only be used in TypeScript files; Line 902: return types can only be used in TypeScript files; Line 903: return types can only be used in

... [truncated 18505 characters] ...

feature. Convert your file to a TypeScript file or remove the syntax.; Line 2680: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2680: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2793: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2808: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2810: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2821: type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

File contains syntax errors that prevent linting: Line 3: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 160: classes can only implement interfaces in TypeScript files; Line 162: return types can only be used in TypeScript files; Line 163: return types can only be used in TypeScript files; Line 164: return types can only be used in TypeScript files; Line 165: return types can only be used in TypeScript files; Line 166: return types can only be used in TypeScript files; Line 167: return types can only be used in TypeScript files; Line 167: expected a semicolon to end the class property, but found none; Line 167: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '; Line 167: type parameters are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 167: Expected a class parameters but instead found ';'.; Line 168: ret

... [truncated 15890 characters] ...

Illegal return statement outside of a function; Line 1280: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 1281: Illegal return statement outside of a function; Line 1291: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 1294: Illegal return statement outside of a function; Line 1297: expected , but instead found :; Line 1297: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 1311: Illegal return statement outside of a function; Line 1314: expected , but instead found :; Line 1314: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 1315: Illegal return statement outside of a function; Line 1319: Expected a statement but instead found '}'.

src/Controllers/DatabaseController.js

File contains syntax errors that prevent linting: Line 18: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 19: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 20: 'import type' are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 113: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 114: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 115: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 116: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 117: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Li

... [truncated 20218 characters] ...

onvert your file to a TypeScript file or remove the syntax.; Line 2102: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2102: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2138: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2138: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2138: return type annotation are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.; Line 2168: return types can only be used in TypeScript files; Line 2169: return types can only be used in TypeScript files

  • 1 others

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

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.66%. Comparing base (cce91e5) to head (c90c6e0).
⚠️ Report is 1 commits behind head on alpha.

Files with missing lines Patch % Lines
...dapters/Storage/Postgres/PostgresStorageAdapter.js 90.00% 1 Missing ⚠️
src/Controllers/DatabaseController.js 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            alpha   #10541      +/-   ##
==========================================
- Coverage   92.66%   92.66%   -0.01%     
==========================================
  Files         193      193              
  Lines       16981    16996      +15     
  Branches      248      248              
==========================================
+ Hits        15736    15749      +13     
- Misses       1224     1226       +2     
  Partials       21       21              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dblythy dblythy marked this pull request as draft July 4, 2026 16:53
$relatedTo (relatedIds) reads the _Join tables by owningId and reverse relation/role queries (owningIds) read them by relatedId, but these were unindexed on Mongo (Postgres only had the relatedId-first primary key). Add a covering index for each direction, auto-created with new relation fields and backfilled for existing ones on startup, gated by the new databaseOptions.createIndexJoinTables option (Postgres backfill uses CREATE INDEX CONCURRENTLY to avoid locking writes).

Closes parse-community#9600
@dblythy dblythy marked this pull request as ready for review July 4, 2026 17:42
@dblythy

dblythy commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

1222-1228: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Backfill may create empty join collections.

createIndex in MongoDB implicitly creates the collection if it does not exist. Since startup backfill derives join tables from every Relation field in the schema, this can materialize empty _Join:<field>:<class> collections for relations that have never had entries added. Consider skipping index creation when the collection is absent to avoid phantom collections.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Adapters/Storage/Mongo/MongoStorageAdapter.js` around lines 1222 - 1228,
The startup backfill in ensureJoinTableIndexes is creating phantom join
collections because createIndex implicitly materializes missing MongoDB
collections. Update ensureJoinTableIndexes in MongoStorageAdapter to first check
whether each join table already exists, and only then call createIndex for the
owningId/relatedId and relatedId/owningId indexes; otherwise skip it so absent
_Join:* collections are not created during startup.
src/Adapters/Storage/Postgres/PostgresStorageAdapter.js (1)

2656-2670: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

CREATE INDEX CONCURRENTLY can leave an invalid index that IF NOT EXISTS won't repair.

If a concurrent build is interrupted (crash, cancellation, deadlock), Postgres leaves an INVALID index behind. On the next startup, IF NOT EXISTS sees the existing (invalid) index and skips it, so the index stays broken and unused indefinitely. Consider detecting/DROP-ing invalid indexes (via pg_index.indisvalid) before re-creating, or documenting the manual remediation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Adapters/Storage/Postgres/PostgresStorageAdapter.js` around lines 2656 -
2670, The current ensureJoinTableIndexes flow can skip rebuilding a broken
concurrent index because CREATE INDEX CONCURRENTLY IF NOT EXISTS will not repair
an INVALID index left behind by an interrupted build. Update
ensureJoinTableIndexes in PostgresStorageAdapter.js to detect existing invalid
indexes for each join table (for example by checking pg_index.indisvalid for the
name from joinTableIndexName) and drop them before retrying creation, or add
explicit guidance for manual cleanup if you prefer not to automate it. Keep the
existing logger.warn handling, but make sure the index creation path can recover
from invalid leftovers instead of silently skipping them.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/Adapters/Storage/Mongo/MongoStorageAdapter.js`:
- Around line 1222-1228: The startup backfill in ensureJoinTableIndexes is
creating phantom join collections because createIndex implicitly materializes
missing MongoDB collections. Update ensureJoinTableIndexes in
MongoStorageAdapter to first check whether each join table already exists, and
only then call createIndex for the owningId/relatedId and relatedId/owningId
indexes; otherwise skip it so absent _Join:* collections are not created during
startup.

In `@src/Adapters/Storage/Postgres/PostgresStorageAdapter.js`:
- Around line 2656-2670: The current ensureJoinTableIndexes flow can skip
rebuilding a broken concurrent index because CREATE INDEX CONCURRENTLY IF NOT
EXISTS will not repair an INVALID index left behind by an interrupted build.
Update ensureJoinTableIndexes in PostgresStorageAdapter.js to detect existing
invalid indexes for each join table (for example by checking pg_index.indisvalid
for the name from joinTableIndexName) and drop them before retrying creation, or
add explicit guidance for manual cleanup if you prefer not to automate it. Keep
the existing logger.warn handling, but make sure the index creation path can
recover from invalid leftovers instead of silently skipping them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7dead766-7af8-4801-930b-4524e6de0ee2

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9d53a and c90c6e0.

📒 Files selected for processing (9)
  • spec/ParseRelation.spec.js
  • src/Adapters/Storage/Mongo/MongoStorageAdapter.js
  • src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
  • src/Adapters/Storage/StorageAdapter.js
  • src/Controllers/DatabaseController.js
  • src/Options/Definitions.js
  • src/Options/docs.js
  • src/Options/index.js
  • src/defaults.js
💤 Files with no reviewable changes (1)
  • src/Adapters/Storage/StorageAdapter.js

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.

Relational query with .limit() and descending order does not work as expected.

1 participant