Skip to content

feat: let a search index's text mapping declare alternates - #84

Merged
kerem-acer merged 1 commit into
mainfrom
feat/search-field-multi
Oct 18, 2026
Merged

kerem-acer merged 1 commit into
mainfrom
feat/search-field-multi

Conversation

@kerem-acer

Copy link
Copy Markdown
Contributor

Closes #83.

SearchField.String(analyzer, searchAnalyzer) couldn't declare multi: the alternate analyzers MongoDB Search also indexes a string with, and which a query picks by name with path: { value, multi }. A field that needed one had to be written whole with SearchField.Raw, or the whole index with Definition(BsonDocument). That's what keeps core's 12 search indexes on raw BSON: 134 of their 135 string fields carry a keyword alternate for wildcard.

What it does

  • SearchField.Multi(name, alternate) returns the text mapping with one more alternate. Each call adds one, so a field can have several, and the mapping it's called on is left as it was:

    products.SearchIndex("default", index => index
        .Analyzer("lucene.turkish")
        .Map(x => x.Name,
            SearchField.String("lucene.turkish").Multi("keyword", SearchField.String("lucene.keyword")),
            SearchField.Autocomplete(2, 20),
            SearchField.Token()));

    It renders multi: { keyword: { type: "string", analyzer: "lucene.keyword" } } on the string mapping, exactly as core writes it by hand. A declaration moved from Definition to Map then matches the index already on the server, and nothing is rebuilt. Comparing listed definitions already handled multi, defaults filled into alternates included, so that needed no change.

  • What MongoDB Search refuses, Multi refuses up front with ArgumentException. I checked each case against Atlas Local 8.3.4:

    Case Server's error
    multi on a non-text mapping unrecognized field "multi"
    A non-text alternate multi definition must be string field
    An alternate with alternates of its own multi definition cannot contain nested multi definition

    Multi also throws for a name already taken or a blank one.

docs/search.md shows Multi under the SearchField table, with a wildcard that picks the alternate through SearchPath.Analyzer.

Tests

SearchTests (integration, on Atlas Local and community-search):

  • MigrateAllAsync_TextWithAnAlternateAsDeclared_LeavesItForSearchesToPick:
    • the second migration logs both indexes as defined as declared;
    • a wildcard of Ada L* on the keyword alternate finds Ada Lovelace;
    • each server's listing is verified: Atlas Local fills the alternate's defaults in, community-search lists it as created.

SearchFieldTests (unit):

  • Multi_TextAlternates_AreWrittenUnderTheTextsMulti: two alternates, and one added to a Raw mapping that has one already.
  • Multi_OfAMapping_LeavesItAsItWas
  • Multi_AlternateMongoDbCantTake_ThrowsArgumentException, which covers:
    • a token mapping;
    • a token alternate;
    • an alternate with alternates;
    • a name taken;
    • a blank name.
  • Multi_NullAlternate_ThrowsArgumentNullException

SearchIndexBuilderTests (unit):

  • SearchIndex_TextWithAnAlternate_IsMappedWithItBesideTheFieldsOtherMappings: the issue's example renders with the field's autocomplete and token mappings, and the features' fields.

Ran locally on net10.0; CI runs net11.0 as well:

  • unit lane: all pass except the two QueryAsync_JoinOn*_IsLeftToTheDriver tests, which fail only locally, against the InsurUp driver fork;
  • SearchTests: all 48 pass on both servers.

🤖 Generated with Claude Code

SearchField.String() couldn't declare multi, the alternate analyzers
MongoDB Search also indexes a string with, which a query picks by name
with path: { value, multi }. A field that needed one had to be written
whole with SearchField.Raw, or the whole index with Definition.

SearchField.Multi(name, alternate) returns the text mapping with one
more alternate, rendered as multi: { name: { type: "string", ... } },
as written by hand, so a declaration moved from Definition to Map
matches the index already on the server. MongoDB Search takes multi
only on text, of text alternates without alternates of their own, so
Multi throws ArgumentException otherwise, and for a name taken.

Closes #83

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@kerem-acer
kerem-acer merged commit a32b708 into main Oct 18, 2026
2 checks passed
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.

Search index builder: let a string mapping declare multi analyzers

1 participant