# field search (Matches / TextScore / Similarity)

> Rank an entity's own rows by a query on an [Searchable(Entity)] field. Matches is the keyword filter (bool), TextScore is the full-text relevance score, Similarity is the semantic (meaning) score. They return values you compare, order, and weight yourself — compose your own hybrid ranking with plain arithmetic.

<!-- id: memory-field-search · area: memory · stability: stable · html: https://osysharp.com/reference/memory/field-search/ -->

## Summary        {#summary}
Field search ranks an entity's **own rows** by a query against one of its `[Searchable(Entity)]` fields (see
[[Searchable]](https://osysharp.com/reference/memory/searchable/)). Three primitives give you the pieces, and you fuse them yourself:

- **`Prop.Matches(q)`** → `bool` — does the field match the keyword query? (the indexed candidate filter)
- **`Prop.TextScore(q)`** → `decimal` — full-text relevance (higher = better keyword match)
- **`Prop.Similarity(q)`** → `decimal` — semantic similarity (higher = closer in meaning)

Each returns a **value**, never a magic ranking. You compare, order, and weight them with ordinary expressions, so
*you* decide what "relevant enough" means — the compose-your-own-hybrid idiom below.

## Signature      {#signature}
```osy syntax
bool    Prop.Matches(string q)              // keyword predicate — use in Where
decimal Prop.TextScore(string q)            // full-text relevance score
decimal Prop.Similarity(string q)           // semantic score on a [Searchable(_, Full)] text field
decimal Prop.Similarity(Vector q)           // semantic score on a raw `Vector` field (bring your own vector)
```

## Description    {#description}
These primitives operate on a single entity's rows through an ordinary query — they behave exactly as any other
query does.

### They run in the query, and only there   {#query-only}

All three are database index operations: `Matches` and `TextScore` work against a full-text index built over the
field, and `Similarity` is a vector distance. None of them has an in-memory form, so they can be used **only inside a
query the database executes** — not on a row you have already loaded, not in a computed member, and not on the client.
Using one anywhere else is a compile error that says so.

```osy title="✗ these run in the query only, never on a row you hold" syntax
Note.Where(n => n.Body.Matches(term))   // ✓ the database answers it, using the index
loadedNote.Body.Matches(term)           // ✗ compile error — there is nothing to run it against here
```

If you need the answer on a row you are holding, ask the query for it: filter or order by these in the query that
loads the rows, and use what it returns.

### `Matches` — the indexed keyword filter   {#matches}
`Prop.Matches(q)` is a boolean full-text predicate: it's true when the field matches the keyword query `q`. It is
**index-backed**, so it's the efficient way to narrow a large table to the candidate rows before you rank them —
use it in `Where`.

### `TextScore` — full-text relevance   {#textscore}
`Prop.TextScore(q)` scores how well the field matches the keyword query (higher = better). Unlike `Matches`, a bare
score is **not** index-backed, so ranking by it alone would scan the whole table — filter with `Matches` first, then
order the survivors by `TextScore`.

### `Similarity` — semantic relevance   {#similarity}
`Prop.Similarity(q)` scores how close the field is to the query **in meaning** (higher = closer), so it finds
matches with no shared keywords. On a `[Searchable(_, Full)]` **text** field you pass a **string** and the engine
embeds it for you (once per query, never per row). On a raw `Vector` field you pass a **vector** you supply yourself.

`Similarity` needs a vector to compare against, so it's available only where one exists: a `Full`-mode searchable
field or a raw `Vector` field. On a `[Searchable(Entity, TextOnly)]` field (no vector) it's a compile error — use
`Matches`/`TextScore` there. Semantic ranking is active only when an embedding provider is configured; without one,
`Similarity` contributes nothing and your search degrades to full-text (see [[Searchable]](https://osysharp.com/reference/memory/searchable/)).

### Runtime thresholds   {#thresholds}
Because each primitive is a value, a relevance cutoff is just a comparison — the bound can be any expression (a
local, a parameter, a literal): `Where(c => c.Bio.Similarity(q) > minScore)`.

### Compose your own hybrid   {#hybrid}
The platform hands you the pieces; you fuse them with plain arithmetic and choose the weights. The idiomatic hybrid
filters with the indexed `Matches`, then orders by a weighted blend of semantic and lexical relevance:

```osy title="fuse the pieces yourself — indexed filter, then weighted rank" syntax
Candidate
  .Where(c => c.Bio.Matches(q))                                          // indexed candidate set
  .OrderByDescending(c => 0.7 * c.Bio.Similarity(q) + 0.3 * c.Bio.TextScore(q))
  .Take(k)
```

For a **turnkey** cross-entity search that fuses these for you over the shared corpus, use [using Memory (semantic search)](https://osysharp.com/reference/memory/search/) instead;
reach for field search when you want entity-local results and control over the ranking.

## Examples       {#examples}
```osy title="keyword filter + full-text ranking" test app=memory
using Osysharp.Memory;

entity Article {
  [Searchable] string Body;
}

List<Article> Search(string q) {
  return Article
    .Where(a => a.Body.Matches(q))                     // indexed candidate filter
    .OrderByDescending(a => a.Body.TextScore(q))       // rank the survivors
    .ToList();
}
```

```osy title="semantic top-k with a runtime threshold" test app=memory
using Osysharp.Memory;

entity Candidate {
  [Searchable] string Bio;
}

List<Candidate> Best(string q, decimal minScore, int k) {
  return Candidate
    .Where(c => c.Bio.Similarity(q) > minScore)        // threshold is any expression
    .OrderByDescending(c => c.Bio.Similarity(q))
    .Take(k)
    .ToList();
}
```

```osy title="compose your own hybrid ranking" test app=memory
// Same `Candidate` as above — you decide how lexical and semantic scores are weighed.
List<Candidate> Hybrid(string q, int k) {
  return Candidate
    .Where(c => c.Bio.Matches(q))
    .OrderByDescending(c => 0.7 * c.Bio.Similarity(q) + 0.3 * c.Bio.TextScore(q))
    .Take(k)
    .ToList();
}
```

## See also       {#see-also}
- [[Searchable]](https://osysharp.com/reference/memory/searchable/) — the `[Searchable]` attribute that makes a field searchable (scope × mode)
- [using Memory (semantic search)](https://osysharp.com/reference/memory/search/) — turnkey hybrid search over the shared corpus (`Memory`-scope fields)
- [SearchHit](https://osysharp.com/reference/memory/searchhit/) — the result type `Memory.Search` returns
