# Searching

Searches are performed on the source table, optionally narrowed by a base query and by selected facet values.
A search returns two things together: the matching rows, and the facet data for those matches - which facet values are available and how many results each would yield.

A base query can restrict the search further - for example, to a text match or a subset of rows - and the results
can be sorted and paginated.

Facet counts are always computed over the full set of matching rows, independent of sorting and pagination,
so paging through results does not change the facets.

## Creating a search query

Use `Refine.search/2` to perform a search.

### Parameters

- `config` is the configuration map used to create the facets table. Here it is used to read references to the source and facet tables.
- `options` may include a base query, selected facet values, and fields to return in the results.

### Options

See the following sections.

> #### Faceted search together with Flop {: .info}
>
> The **Flop adapter** translates Flop parameters into a Refine faceted search:
> [Flop ➚](https://hex.pm/packages/flop) provides filtering, ordering, and pagination, while Refine provides the facets.
> This is useful when you already use Flop for searching and want to add faceting to existing queries.
>
> See [Flop adapter](flop_adapter.md).

### Examples

```elixir
Refine.search(config,
  facets: %{"draft" => ["true"]},
  order_by: [{"updated_at", :desc}],
  repo: Repo
)
```

The response includes the matching data rows, the available facets and facet options (labels, values and counts) for display, plus facet type information.

```elixir
Refine.search(config, options)
{:ok,
  %{
    rows: [...],
    facets: %{...},
    types: %{...},
    total_count: 999
  }
}
```

See [Return value ↓](#return-value) for an example of the shape of this data.

## Parameters

- `config` is the configuration map used to create the facets table. Here it is used to read references to the source and facet tables.
- `options` may include a base query, selected facet values, and fields to return in the results.

## query

Provide a base query, for example to list products from a specific category or apply a text search.

### Examples

With `where` filter and `select` fields:

```elixir
query = from a in Article,
  where: ilike(a.title, ^"%memory%"),
  select: %{id: a.id, title: a.title},

Refine.search(config,
  query: query
)
```

With `select` fields from joined tables:

```elixir
query =
  from article in Article,
    left_join: author_article in AuthorArticle,
    on: article.id == author_article.article_id,
    left_join: author in Author,
    on: author.id == author_article.author_id,
    left_join: role in Role,
    on: role.id == author.role_id,
    select: %{
      title: article.title,
      summary: article.summary,
      author: author.full_name,
      role: role.name
    }

Refine.search(config,
  query: query
)
```

You may provide pagination attributes in the query attributes:

```elixir
query = from a in Article,
  limit: 10,
  offset: 10

Refine.search(config,
  query: query
)
```

For more query options, see: [Ecto.Query ➚](https://hexdocs.pm/ecto/Ecto.Query.html).

## facets

The `facets` option is a map of the currently selected values for each facet.
The map keys are facet names, as defined in the configuration; the values are the selected
option values as stored in the facets table.

Facet option values are always strings.

These selections narrow the search: only rows matching the selected values are returned.
Facet data itself comes from the facets table, which holds precomputed counts for every facet value.

For each facet, the search reports which values are still available given the current selections -
so a user can see what further filtering is possible and how many results each choice would yield.
As selections narrow the results, the available facet values narrow with them.

### Examples

```elixir
Refine.search(config,
  facets: %{
    "draft" => ["true"],
    "color" => ["blue", "red"]
  }
)
```

If a facet has multiple selected values (here: blue and red), the filter for that facet is applied using an OR operator. In this case, the logic becomes: `draft=true AND (color=blue OR color=red)`.

## order_by

Sorts the search results.

`order_by` takes a list of tuples, each in one of two forms:

- `{column, direction}`
- `{column, direction, null_sort}`

Where:

- Value `column` is the column name (a string).
- Value `direction` is `:asc` or `:desc`.
- Value `null_sort` defines how null values are placed: `:first`, `:last` or `:default`.
  With `:default` (or the two-element form), the database default applies - nulls last for `:asc`, nulls first for `:desc`.

You can sort by:

- Any column of the source table, whether or not it is included in the query's `select`
- Any field included in the query's `select`, including joined fields (referenced by their select key)

Sorting by a column that is neither a source-table column nor a selected field raises an error.

Results are always ordered by the identity column as a final tiebreaker, so the ordering is stable even when the sorted columns contain duplicate values.

### Examples

Sort by a single source column, descending:

```elixir
Refine.search(config,
  order_by: [{"updated_at", :desc}],
  repo: Repo
)
```

Sort by multiple source columns:

```elixir
order_by: [{"updated_at", :desc}, {"title", :asc}]
```

Sort by a source column that isn't selected:

```elixir
query = from a in Article, select: %{id: a.id, title: a.title}

Refine.search(config,
  query: query,
  order_by: [{"updated_at", :desc}]
  repo: Repo,
)
```

Sort by a joined and selected field, referenced by its select key:

```elixir
query =
  from a in Article,
    join: c in assoc(a, :category),
    select: %{id: a.id, category_name: c.name}

Refine.search(config,
  query: query,
  order_by: [{"category_name", :asc}],
  repo: Repo
)
```

Sort with null values first:

```elixir
order_by: [{"title", :asc, :first}]
order_by: [{"title", :desc, :first}]
```

Sort with null values last:

```elixir
order_by: [{"title", :asc, :last}]
order_by: [{"title", :desc, :last}]
```

Default null placement depends on direction:

```elixir
order_by: [{"title", :asc}]   # nulls last (Postgres default for :asc)
order_by: [{"title", :desc}]  # nulls first (Postgres default for :desc)
```

## limit and offset

Paginate the search results using `limit` and `offset`.

If pagination is used, both `limit` and `offset` must be specified.

If neither option is specified, the following defaults are used:

- `limit`: 10
- `offset`: 0

### Examples

```elixir
Refine.search(config,
  query: query,
  limit: 5,
  offset: 2
)
```

## result_fields

The `result_fields` option specifies which fields to include in the result set.
It provides a convenient alternative to defining a custom query.
Use it to limit the returned columns or to include fields that are not defined in the source Ecto schema.

`result_fields` takes precedence over any `select` expression in `query`.

> #### Joined tables {: .warning}
>
> `result_fields` only works with fields from the source table. When selecting fields
> from joined tables, use `Ecto.Query.select/3` on the query instead.

### Examples

```elixir
Refine.search(config,
  result_fields: ["identity", "title"]
)
```

## repo and timeout

- `repo` - The Ecto Repo module that contains a Postgres adapter. Omit when the repo is configured globally.
- `timeout` - Postgrex timeout option.

## Return value

The returned map is quite similar to an [Elasticsearch facets query result ➚](https://www.elastic.co/guide/en/app-search/current/facets-guide.html):

```elixir
{:ok,
  %{
    total_count: 999,
    rows: [
      %{
        "draft" => "true",
        "identity" => 1,
        "title" => "Géographie des marges",
        ...
      },
      ...
    ],
    facets: %{
      "draft" => %{
        "buckets" => [
          %{
            "count" => 6,
            "label" => "false",
            "selected" => false,
            "value" => "false"
          },
          %{
            "count" => 4,
            "label" => "true",
            "selected" => false,
            "value" => "true"
          }
        ],
        "label" => "draft"
      },
      ...
    },
    types: %{
      "draft" => "boolean",
      "identity" => "integer",
      "title" => "text",
      ...
    }
  }
}
```
