> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Search

> Hybrid full-text and vector search with RRF reranking

`search()` is the heart of retrieval. It runs a **hybrid** query — a full-text search over `text` and a vector search over the embedded content — and merges the two with an **RRF reranker** (Reciprocal Rank Fusion). You get keyword precision and semantic recall in one ranked list, typed against your `metadata`.

## Basic search

Pass a query string. The query is embedded with the table's [embedding model](/ai/rag/embeddings) and matched against stored vectors and the full-text index at once.

```typescript theme={null}
const results = await table.search("retrieval augmented generation");
// ({ id: string } & { metadata: { ... } })[]
```

Each result is a full record (`{ id, text, metadata }`) ordered by relevance.

## Options

```typescript theme={null}
const results = await table.search("vector databases", {
  limit: 5,
  select: ["title", "category"],
  filter: { field: "category", op: "=", value: "AI" },
  nprobes: 20,
  refineFactor: 10,
  fastSearch: true,
});
```

| Option         | Type                                   | Default     | Description                                                                        |
| -------------- | -------------------------------------- | ----------- | ---------------------------------------------------------------------------------- |
| `limit`        | `number`                               | `10`        | Maximum number of results to return.                                               |
| `select`       | `(keyof metadata \| "id" \| "text")[]` | all columns | Columns to return; `id` is always included.                                        |
| `filter`       | `Filter<DataType>`                     | —           | Conditions to scope results. See [Filtering](/ai/rag/filtering).                   |
| `nprobes`      | `number`                               | —           | Number of IVF partitions to search. Higher values improve recall but cost speed.   |
| `refineFactor` | `number`                               | —           | Multiplier for extra candidates during the IVF-PQ refine step, improving accuracy. |
| `fastSearch`   | `boolean`                              | `true`      | Skip un-indexed data for faster queries when indexes are up to date.               |

### Selecting columns

`select` limits which columns come back. Use your `metadata` field names — they resolve to the underlying columns automatically — alongside `id` and `text`. `id` is always included even if you omit it.

```typescript theme={null}
const results = await table.search("billing", { select: ["title"] });
```

### Filtering results

`filter` narrows results to matching rows before ranking. Conditions compose with `AND`, `OR`, and `NOT`.

```typescript theme={null}
const results = await table.search("machine learning", {
  limit: 10,
  filter: {
    AND: [
      { field: "category", op: "=", value: "AI" },
      { NOT: { field: "title", op: "LIKE", value: "%draft%" } },
    ],
  },
});
```

See [Filtering](/ai/rag/filtering) for the full operator set.

## Tuning recall and speed

The vector half of the search uses an IVF-PQ index, which trades exactness for speed. Three knobs let you tune that trade-off:

* **`nprobes`** — how many index partitions to scan. More partitions find more true neighbors (higher recall) but take longer. Raise it when results miss relevant rows.
* **`refineFactor`** — how many extra candidates to re-rank with full-precision vectors before returning. Higher values improve ranking accuracy at some cost.
* **`fastSearch`** — when `true` (the default), only indexed data is searched. If you have just added rows and want them included before the index catches up, set it to `false`.

<Tip>
  Start with the defaults. Increase `nprobes` first if results feel incomplete, then add a small `refineFactor` (e.g. `10`) if the ordering needs sharpening. Measure on your own data — the right values depend on table size and embedding model.
</Tip>

## Inspecting query plans

When a query is slower than expected, inspect its plan to see which indexes are used and where time goes. Both methods accept `limit` and `filter` like `search()`.

### `explainPlan()`

Returns the resolved query plan as a string without executing the query. Useful for confirming an index is being used.

```typescript theme={null}
const plan = await table.explainPlan("retrieval augmented generation", {
  limit: 10,
  verbose: true,
});
console.log(plan);
```

| Option    | Type               | Default | Description                        |
| --------- | ------------------ | ------- | ---------------------------------- |
| `limit`   | `number`           | `10`    | Result limit used when planning.   |
| `filter`  | `Filter<DataType>` | —       | Filter applied in the plan.        |
| `verbose` | `boolean`          | `true`  | Include detailed plan information. |

### `analyzePlan()`

Executes the query and returns a physical plan annotated with runtime metrics — rows scanned, time per stage, and so on. Use it to find the actual bottleneck.

```typescript theme={null}
const analysis = await table.analyzePlan("retrieval augmented generation", {
  limit: 10,
});
console.log(analysis);
```

<Note>
  `analyzePlan()` runs the query for real to collect metrics, while `explainPlan()` only resolves the plan. Reach for `explainPlan()` first; use `analyzePlan()` when you need measured timings.
</Note>
