Skip to content

Support TTL indexes in the GORM-Mongo mapping DSL and reconcile index option changes#15747

Open
codeconsole wants to merge 2 commits into
apache:8.0.xfrom
codeconsole:mongo-ttl-index-support
Open

Support TTL indexes in the GORM-Mongo mapping DSL and reconcile index option changes#15747
codeconsole wants to merge 2 commits into
apache:8.0.xfrom
codeconsole:mongo-ttl-index-support

Conversation

@codeconsole

Copy link
Copy Markdown
Contributor

What

Two related improvements to MongoDB index initialisation in MongoDatastore.initializeIndices.

1. TTL indexes can be declared in the mapping DSL

A single-field index declared with expireAfterSeconds now materialises a MongoDB TTL index:

static mapping = {
    created index: true, indexAttributes: [expireAfterSeconds: 7776000]   // 90 days
}

Previously this was impossible. MongoConstants.mapToObject builds IndexOptions by reflectively invoking same-named single-argument setters, but the driver's only TTL setter is the two-argument IndexOptions.expireAfter(Long, TimeUnit), which the mapper can't reach. So expireAfterSeconds is now pulled out of the index attributes and applied explicitly.

TTL is single-field only — MongoDB rejects expireAfterSeconds on a compound index with CannotCreateIndex; that error surfaces unchanged.

2. Index-option conflicts are reconciled, not just logged

All three index paths (declared getIndices(), getCompoundIndices(), per-property) now route through one helper. On IndexOptionsConflict (server error 85 — an index already exists on these keys with different options) it reconciles instead of only logging the stack trace:

  • TTL value change (e.g. a configurable retention changed between restarts) is applied in place via collMod — no drop, no rebuild, no window without an index.
  • Any other conflicting change is dropped and recreated only when indexAttributes: [recreateOnConflict: true] is declared; otherwise it logs a clear, actionable message. A unique index is never silently dropped.

Why

Without DSL support, a TTL index has to be created imperatively (raw-driver createIndex in a bootstrap hook), and changing its retention needs hand-rolled drop-and-recreate logic. That is easy to get wrong: a stale plain index on the same key blocks the TTL create, and the TTL then silently never applies. Declaring it in the mapping, with in-place reconciliation handled by the framework, removes both the boilerplate and that footgun.

Tests

TtlIndexSpec (runs against the Mongo testcontainer) covers:

  • expireAfterSeconds in the mapping produces a TTL index with the declared value.
  • Mutating the live TTL and re-initialising restores the declared value on the same index (one index, no conflict) — exercising the collMod reconcile path.

Existing IndexAttributesAndCompoundKeySpec (unique / compound) still passes.

… option changes

indexAttributes: [expireAfterSeconds: N] now materialises a MongoDB TTL index.
The driver only exposes the two-argument IndexOptions.expireAfter, which
MongoConstants.mapToObject cannot reach (it only invokes single-argument
setters), so expireAfterSeconds is pulled out of the attributes and applied
explicitly.

All three index paths in MongoDatastore.initializeIndices (declared, compound,
and per-property) now route through a single helper. On IndexOptionsConflict
(error 85) it reconciles instead of only logging: a TTL change is applied in
place with collMod (no drop, no rebuild, no gap); any other conflicting change
is dropped and recreated only when indexAttributes: [recreateOnConflict: true]
is declared, otherwise logged with guidance.

Adds TtlIndexSpec covering TTL creation from the mapping declaration and
in-place TTL reconciliation on re-initialisation.
…hetic _fts key

A declared text index has key {field: 'text'}, but MongoDB reports an existing one with a
synthetic {_fts: 'text', _ftsx: 1} key, so the IndexOptionsConflict reconcile never matched it
and recreateOnConflict couldn't absorb a differently-named text index. Since MongoDB allows at
most one text index per collection, an existing text index is unambiguously the conflicting one
— findIndexByKeyPattern now matches any existing text index when the declared index is itself a
text index, regardless of key shape or name.

Adds TextIndexViaAttributesSpec covering conventional text-index declaration via
indexAttributes: [type: 'text'] and absorption of a legacy differently-named text index.
@testlens-app

testlens-app Bot commented Jun 19, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: b939e01
▶️ Tests: 40374 executed
⚪️ Checks: 44/44 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant