# `Cinder.QueryBuilder`
[🔗](https://github.com/sevenseacat/cinder/blob/v0.18.0/lib/cinder/query_builder.ex#L5)

Query building functionality for Cinder table components.

Handles the construction of Ash queries with filters, sorting, and pagination
for table data loading.

# `column`

```elixir
@type column() :: %{
  field: String.t(),
  filterable: boolean(),
  filter_type: atom(),
  filter_fn: function() | nil
}
```

# `filter`

```elixir
@type filter() :: %{type: atom(), value: any(), operator: atom()}
```

# `filters`

```elixir
@type filters() :: %{required(String.t()) =&gt; filter()}
```

# `query_opts`

```elixir
@type query_opts() :: [
  load: term(),
  select: list(),
  tenant: term(),
  timeout: timeout(),
  authorize?: boolean(),
  max_concurrency: pos_integer()
]
```

# `sort_by`

```elixir
@type sort_by() :: [{String.t(), :asc | :desc}]
```

# `apply_filters`

Applies filters to an Ash query based on filter configuration and column definitions.

# `apply_query_opts`

Applies query options like load and select to an Ash query.

Warns if unsupported options are provided.

# `apply_search`

Applies global search to an Ash query across searchable columns.

## Parameters
- `query`: The Ash query to modify
- `search_term`: The search term to filter by (empty/nil terms are ignored)
- `columns`: List of column definitions to find searchable fields
- `custom_search_fn`: Optional table-level custom search function

## Custom Search Function Signature
`search_fn(query, searchable_columns, search_term)`

## Returns
The modified query with search conditions applied, or the original query
if no search term is provided or no searchable columns exist.

## Examples

    # Default search across searchable columns
    query = apply_search(query, "widget", columns, nil)

    # Custom search function
    def custom_search(query, searchable_columns, search_term) do
      # Custom implementation
    end
    query = apply_search(query, "widget", columns, &custom_search/3)

# `apply_sorting`

Applies sorting to an Ash query based on sort specifications.

# `apply_standard_filter`

Applies standard filters by delegating to the appropriate filter module.

# `build_and_execute`

Builds a complete query with filters, sorting, and pagination, then executes it.

## Parameters
- `resource_or_query`: The Ash resource to query or a pre-built Ash.Query
- `options`: Query building options including:
  - `:actor` - The current user/actor
  - `:filters` - Filter map
  - `:sort_by` - Sort specifications
  - `:tiebreakers` - Map of a sortable column's field to its `then` fields
  - `:page_size` - Number of records per page
  - `:current_page` - Current page number
  - `:columns` - Column definitions
  - `:query_opts` - Additional Ash query and execution options
  - `:search_term` - Global search term to search across searchable columns
  - `:search_fn` - Optional custom search function with signature `(query, searchable_columns, search_term)`

## Supported Query Options

The `:query_opts` parameter accepts both query building and execution options:

### Query Building Options
- `:select` - Select specific attributes (handled by `Ash.Query.select/2`)
- `:load` - Load relationships and calculations (handled by `Ash.Query.load/2`)

### Execution Options
These options are passed to both `Ash.Query.for_read/3` and `Ash.read/2`:
- `:timeout` - Query timeout in milliseconds or `:infinity` (e.g., `:timer.seconds(30)`)
- `:authorize?` - Whether to run authorization during query execution
- `:max_concurrency` - Maximum number of processes for parallel loading
- `:tracer` - Tracer module(s) to use for the query (see `Ash.Tracer`)

### Usage Examples

    # Simple timeout for long-running queries
    query_opts: [timeout: :timer.seconds(30)]

    # Query building options
    query_opts: [select: [:name, :email], load: [:posts]]

    # Combined query building and execution options
    query_opts: [
      timeout: :timer.seconds(20),
      authorize?: false,
      select: [:title, :content],
      load: [:author, :comments]
    ]

## Returns

Returns `{:ok, page}` on success or `{:error, reason}` on failure.

The `page` value depends on the pagination mode and action configuration:

- **Offset pagination** (`:pagination_mode` is `:offset`, default): Returns `Ash.Page.Offset` struct
- **Keyset pagination** (`:pagination_mode` is `:keyset`): Returns `Ash.Page.Keyset` struct
- **Non-paginated actions**: Returns `%{results: list()}` map (not a struct)

All return types support accessing results via `page.results`.

Note: Non-paginated actions return a plain map rather than an Ash.Page struct.
This means pattern matching on `%Ash.Page.Offset{}` or `%Ash.Page.Keyset{}` will
not match non-paginated results. Use `page.results` for consistent access.

# `default_sorts_from_cycles`

Applies default sorts for columns with nil-less sort cycles.

Columns whose `sort_cycle` does not contain `nil` imply "always sorted".
For any such column not already present in `query_sorts`, the first value
in the cycle is appended as a default sort (preserving declaration order).

Query sorts always take precedence.

# `extract_query_sorts`

Extracts sort information from an Ash query for table UI initialization.

Takes an Ash query and returns sort information in the format expected by
the table component: `[{field_name, direction}]`

## Parameters
- `query` - An Ash.Query struct or resource module
- `columns` - Column definitions to map query sorts to table fields

## Returns
A list of `{field_name, direction}` tuples where:
- `field_name` is a string matching table column field names
- `direction` is `:asc` or `:desc`

## Examples

    # Query with sorts
    query = User |> Ash.Query.for_read(:read) |> Ash.Query.sort([{:name, :desc}, {:created_at, :asc}])
    extract_query_sorts(query, columns)
    # => [{"name", :desc}, {"created_at", :asc}]

    # Resource module (no sorts)
    extract_query_sorts(User, columns)
    # => []

# `get_sort_direction`

Gets the current sort direction for a given key.

# `infer_loads`

The loads that showing these column fields needs, for `Ash.Query.load/2`: their
calculations and aggregates, and the relationships in their paths. Attributes and
embedded fields need none.

Fields that can't be resolved on the resource, and calculations with required
arguments, are skipped: a slot showing them needs an explicit `load`.

For example, `["title", "artist.name", "artist.album_count"]` on an album resource
gives `[artist: [:album_count]]`.

# `toggle_sort_direction`

Toggles sort direction for a given key in the sort specification.

Provides a predictable three-step cycle:
- none → ascending → descending → none

# `toggle_sort_with_cycle`

Toggles sort direction using custom cycle configuration.

Supports custom sort cycles like [nil, :desc_nils_last, :asc_nils_first].

Falls back to standard toggle_sort_direction/2 if no custom cycle provided.

## Options

* `sort_mode` - `:additive` (default) adds to existing sorts, `:exclusive` replaces them

# `validate_sortable_fields`

Validates that all fields in a sort list can be sorted at the database level.

Checks each sort field to ensure it's not an in-memory calculation that would
cause crashes or undefined behavior when sorting is attempted.

## Parameters
- `sort_by` - List of `{field, direction}` tuples
- `resource` - Ash resource module

## Returns
- `:ok` if all fields can be sorted
- `{:error, message}` if any fields cannot be sorted

## Examples

    validate_sortable_fields([{"name", :asc}], User)
    # => :ok

    validate_sortable_fields([{"in_memory_calc", :asc}], User)
    # => {:error, "Cannot sort by in-memory calculations..."}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
