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

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.

## 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
  - `: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

### 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.

# `calculation_sortable?`

Determines if a calculation can be sorted at the database level.

Checks if a calculation has an `expression/2` function that allows it to be
converted to a database expression for sorting.

## Parameters
- `calculation` - An Ash calculation struct

## Returns
- `true` if the calculation can be sorted at the database level
- `false` if the calculation is computed in-memory and cannot be sorted

## Examples

    # Database-level calculation (using expr())
    calculation_sortable?(%{calculation: {Ash.Resource.Calculation.Expression, _}})
    # => true

    # In-memory calculation without expression/2
    calculation_sortable?(%{calculation: {MyCalcModule, _}})
    # => false (if MyCalcModule doesn't implement expression/2)

# `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)
    # => []

# `field_exists_on_resource?`

Checks if a field exists on a resource (including attributes, relationships, calculations, aggregates)

# `get_calculation_info`

Retrieves calculation information for a given field from an Ash resource.

## Parameters
- `resource` - Ash resource module
- `field_name` - Field name as atom or string

## Returns
- Calculation struct if the field is a calculation
- `nil` if the field is not a calculation or doesn't exist

## Examples

    get_calculation_info(User, :full_name)
    # => %{name: :full_name, calculation: {...}, ...} or nil

# `get_sort_direction`

Gets the current sort direction for a given key.

# `resolve_field_resource`

Resolves a field to its target resource and field name.
Handles relationship traversal (e.g., "user.profile.first_name" -> {Profile, "first_name"})

# `toggle_sort_direction`

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

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

When starting with extracted query sorts, use `toggle_sort_from_query/2`
for better UX that handles the transition from query state to user control.

# `toggle_sort_from_query`

Toggles sort direction with special handling for query-extracted sorts.

When a column has a sort from query extraction, the first user click
provides intuitive behavior:
- desc (from query) → asc (user takes control)
- asc (from query) → desc (user takes control)

After first click, follows standard toggle cycle.

# `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_field_existence`

Validates field existence on a resource, handling all field types including embedded fields.

Supports:
- Direct fields: "name"
- Relationship fields: "user.profile.name"
- Embedded fields: "profile__first_name" (URL-safe) or "profile[:first_name]" (bracket notation)
- Mixed fields: "user.profile__address__street"

# `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*
