Skip to content

FullTextIndexDefinition cannot express a pre-built tsvector, so setweight() weighting is unreachable #541

Description

@jeremydmiller

Raised from JasperFx/marten#5298, a request for ranked full-text search. Part of that feature — per-member weighting via setweight — cannot be built on the Marten side alone, because this definition unconditionally wraps whatever it is given in to_tsvector.

The constraint

Weasel.Postgresql/Tables/Indexes/FullTextIndexDefinition.cs:47:

public override string[] Columns
{
    get => new[] { $"to_tsvector('{regConfig}',{DocumentConfig.Trim()})" };
    set { /* nothing */ }
}

DocumentConfig is therefore always treated as text to be converted. There is no way to hand this type an expression that is already a tsvector.

Why that blocks the feature

PostgreSQL's weighting works by labelling each member's vector and concatenating the vectors, not the text (textsearch-features):

create index mt_doc_achievement_idx_fts on public.mt_doc_achievement using gin ((
  setweight(to_tsvector('english', coalesce(d.data ->> 'Title', '')), 'A') ||
  setweight(to_tsvector('english', coalesce(d.data ->> 'Tagline', '')), 'B') ||
  setweight(to_tsvector('english', coalesce(d.data ->> 'Description', '')), 'C')
));

The expression is a tsvector at the top level. Passing it as DocumentConfig yields to_tsvector('english', setweight(...) || setweight(...)) — a type error, not a weighted index. So a Marten-side change can produce the right inner text and still not emit this DDL.

Today Marten concatenates the members as text before a single to_tsvector, which is exactly what the current shape supports and why weighting has never come up:

gin (to_tsvector('english', ((d.data ->> 'Title') || ' ' || (d.data ->> 'Description'))))

Shape of a fix

The suggestion in the Marten issue is a second property — say TsVectorExpression — that is consumed by Columns without the to_tsvector wrapping, with DocumentConfig continuing to behave exactly as it does now when it is not set. That keeps every existing index byte-identical and avoids a schema diff for anyone not opting in, which matters because a changed index expression means Weasel drops and recreates the index — on a large table, an outage rather than a migration.

Whatever the shape, the requirement is: one property that both the DDL and the consumer's query-side filter read, so the indexed vector and the searched vector cannot drift apart. Marten's FullTextWhereFragment already reads the expression back off the index definition for exactly this reason, and ranking makes the coupling stricter still — a ts_rank computed over a different vector than the @@ filtered on is silently wrong rather than merely slow.

Not urgent

Nothing is broken today; this is a capability gap. Marten is on Weasel 9.27.0 and the ranking work is not scheduled — the Marten issue splits it so that ts_rank ordering (Marten-only, no dependency here) can land first and setweight follows a Weasel release. Filing now so the dependency is recorded rather than rediscovered when that half comes up.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions