Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions spec/MongoStorageAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { MongoClient, Collection } = require('mongodb');
const databaseURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const request = require('../lib/request');
const Config = require('../lib/Config');
const SchemaController = require('../lib/Controllers/SchemaController');
const TestUtils = require('../lib/TestUtils');
const Utils = require('../lib/Utils');
const { randomUUID: uuidv4 } = require('crypto');
Expand Down Expand Up @@ -900,6 +901,33 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
expect(userIndexes.find(idx => idx.name === '_perishable_token' || idx.name === '_perishable_token_1')).toBeDefined();
expect(roleIndexes.find(idx => idx.name === 'name_1')).toBeDefined();
});

it('ensureIndex tolerates an existing conflicting index instead of crashing startup (#10431)', async () => {
await reconfigureServer({ databaseAdapter: undefined, databaseURI, databaseOptions: {} });
const adapter = Config.get(Parse.applicationId).database.adapter;
const collection = await adapter._adaptiveCollection('_Idempotency');
// Simulate a pre-existing `ttl` index created by an older Parse Server / driver whose stored
// spec conflicts with the one requested on startup (same name + key, different options). This
// produces IndexKeySpecsConflict (code 86) — the same failure MongoDB 8.0 triggers via its
// internal `enableOrderedIndex` field, which previously aborted server startup.
await collection._mongoCollection.dropIndex('ttl').catch(() => {});
await collection._mongoCollection.createIndex(
{ expire: 1 },
{ name: 'ttl', expireAfterSeconds: 100, sparse: true }
);
const schema = {
fields: {
...SchemaController.defaultColumns._Default,
...SchemaController.defaultColumns._Idempotency,
},
};
// Mirrors DatabaseController.performInitialization's TTL-index call, which previously threw.
await expectAsync(
adapter.ensureIndex('_Idempotency', schema, ['expire'], 'ttl', false, { ttl: 0 })
).toBeResolved();
const indexes = await getIndexes('_Idempotency');
expect(indexes.find(idx => idx.name === 'ttl')).toBeDefined();
});
});

describe('logClientEvents', () => {
Expand Down
17 changes: 16 additions & 1 deletion src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,22 @@ export class MongoStorageAdapter implements StorageAdapter {
.then(collection =>
collection._mongoCollection.createIndex(indexCreationRequest, indexOptions)
)
.catch(err => this.handleError(err));
.catch(error => {
// MongoDB 8.0 enriches stored index specs with internal fields (e.g. `enableOrderedIndex`)
// that Parse Server never sets. Re-issuing an otherwise-identical createIndex on startup
// then fails with IndexKeySpecsConflict (86) / IndexOptionsConflict (85) even though a
// functionally-equivalent index already exists — which aborts server startup. Treat that
// as success (the existing index already serves its purpose), mirroring
// ensureAuthDataUniqueness. Logged so a genuine conflict is still visible. (#10431)
if (error.code === 85 || error.code === 86) {
logger.warn(
`Index "${indexName || ''}" on ${className} already exists with a conflicting ` +
`specification; keeping the existing index. (code ${error.code})`
);
return;
}
return this.handleError(error);
});
Comment on lines +821 to +836

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check all call sites of ensureIndex to assess blast radius of blanket-catching 85/86
rg -nP '\bensureIndex\s*\(' --type=js -C2

Repository: parse-community/parse-server

Length of output: 166


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section and nearby helpers.
sed -n '760,880p' src/Adapters/Storage/Mongo/MongoStorageAdapter.js

printf '\n--- SEARCH: codes and related helpers ---\n'
rg -n "IndexOptionsConflict|IndexKeySpecsConflict|ensureAuthDataUniqueness|createIndex|dropIndex|partialFilterExpression|collation|unique" src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Repository: parse-community/parse-server

Length of output: 7474


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- ensureIndex call sites ---\n'
rg -n "\bensureIndex\s*\(" src

printf '\n--- related index management around createIndexesIfNeeded / createIndexes / dropIndex ---\n'
sed -n '1180,1295p' src/Adapters/Storage/Mongo/MongoStorageAdapter.js

printf '\n--- any other 85/86 handling ---\n'
rg -n "85|86|IndexOptionsConflict|IndexKeySpecsConflict" src

Repository: parse-community/parse-server

Length of output: 257


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- ensureIndex call sites ---'
rg -n "\bensureIndex\s*\(" src

echo
echo '--- index lifecycle code around createIndexesIfNeeded/createIndexes/dropIndex ---'
sed -n '1180,1295p' src/Adapters/Storage/Mongo/MongoStorageAdapter.js

echo
echo '--- tests and comments mentioning 85/86 or MongoDB 8.0 index conflicts ---'
rg -n "IndexOptionsConflict|IndexKeySpecsConflict|enableOrderedIndex|`#10431`|ensureAuthDataUniqueness|createIndex\(" test src

Repository: parse-community/parse-server

Length of output: 6507


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- DatabaseController ensureIndex call sites and options ---'
sed -n '1970,2075p' src/Controllers/DatabaseController.js

echo
echo '--- MongoCollection helpers around createIndex / ensure sparse unique index ---'
sed -n '1,240p' src/Adapters/Storage/Mongo/MongoCollection.js

Repository: parse-community/parse-server

Length of output: 9541


Only ignore 85/86 for equivalent indexes This treats every ensureIndex conflict as success, so a real index-definition change can leave a stale index in place on startup. Compare the existing spec first, or drop/recreate when it differs.

🧰 Tools
🪛 Biome (2.5.1)

[error] 817-836: Illegal return statement outside of a function

(parse)

🤖 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 821 - 836,
The current `ensureIndex` catch block in `MongoStorageAdapter` treats every
85/86 conflict as success, which can mask real index-definition changes. Update
the logic in this `catch` path to first compare the existing index spec against
the requested one (using the surrounding `indexName`/`className` index creation
flow), and only ignore the error when they are functionally equivalent. If the
specs differ, either drop and recreate the index or surface the error through
`handleError` instead of returning success.

}

// Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
Expand Down
Loading