Skip to content

Reject a declared field name that collides with a physical companion field #766

Description

@ddeboer

Problem

The physical fanout mints engine field names from a declared field’s name by suffix (physicalFields, displayFieldName): ${name}_search, ${name}_search_<locale>, ${name}_sort_<locale>, the display pattern ${name}_<lang>, and – since the facet policy – the companion ${name}_facet. Nothing stops a schema from declaring a field whose name equals one of these. validateSearchType restricts the name pattern and reserves only id; the companion names are not reserved.

A collision passes validation and breaks silently, wherever the duplicate meets the engine:

  • Collection definition – a type with a policy-inheriting reference about and a declared keyword about_facet emits two Typesense fields named about_facet (one string[], one string). Typesense rejects the create, or keeps one declaration and then fails imports carrying the other shape.
  • Response boundary – the engine maps facet_counts[].field_name back to declared names through a map keyed by engine name (declaredFacetNames in search.ts). The declared field’s identity entry about_facet → about_facet is overwritten by the companion’s about_facet → about, so a facet on the declared field is filed under the wrong key and labelled from the wrong source.
  • Pre-existing class – the same hole already exists for the older companions: a keyword title_search beside a searchable title, a keyword label_nl beside an output text field label (the display pattern is per-language, so that collision is data-dependent).

Surfaced by the review of #765, which added a second companion suffix without adding a check – see “Known, pre-existing hole, not widened here” in its description. Left out there deliberately: it is a schema-wide naming rule, not part of the facet policy.

Options

  1. Reserve the suffix shapes outright: reject any declared name that another declared field could fan out to, regardless of whether that field currently produces the companion (i.e. ignore searchable/sortable/policy state). Simple, predictable, needs no schema – but rejects names that are harmless today and couples the rule to flags a later edit may flip anyway, which is rather the point.
  2. Reject actual collisions only: per type, a declared name may not equal a sibling’s produced companion (_search, _search_<locale>, _sort_<locale>, display _<lang> for its declared locales). The _facet companion exists only when the named target declares facetKeys, so that half is a schema-level check in searchSchema (where the label-source resolvability check already lives). Precise, but the answer changes when a flag or a policy changes.

Leaning towards (1) restricted to collision with a sibling’s name: for every pair of declared fields, reject b.name === a.name + '_' + suffix… shapes without consulting flags – stable under flag changes, still never rejects a name with no sibling to collide with.

Either way this is a new validation failure for schemas that currently declare such names, acceptable pre-1.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions