LiveFilter.QueryBuilder (LiveFilter v0.1.0)

View Source

Converts filter configurations to Ecto queries using dynamic query building.

This module is the core of LiveFilter's database integration. It takes FilterGroup and Sort structures and converts them into efficient Ecto queries that can be executed against your database.

Features

  • Dynamic Query Building: Uses Ecto.Query.dynamic/2 for efficient query composition
  • 20+ Operators: Supports comprehensive filtering operations for all data types
  • Nested Logic: Handles complex AND/OR combinations with FilterGroups
  • PostgreSQL Arrays: Special support for array operations (contains_any, contains_all)
  • Null Handling: Proper handling of nil values and empty checks
  • Multi-column Sorting: Supports single and multiple sort columns

Database Compatibility

  • PostgreSQL: Full support including array operations
  • MySQL: Supports all operators except array operations
  • SQLite: Supports all operators except array operations

Performance Notes

This module generates efficient SQL queries with proper WHERE clauses. For best performance, ensure your database has appropriate indexes on filtered fields.

Examples

# Simple filter
query = from p in Product
filter_group = %FilterGroup{
  filters: [
    %Filter{field: :name, operator: :contains, value: "phone", type: :string}
  ]
}
QueryBuilder.build_query(query, filter_group)

# Complex nested filters with sorting
filter_group = %FilterGroup{
  filters: [
    %Filter{field: :status, operator: :equals, value: "active", type: :enum}
  ],
  groups: [
    %FilterGroup{
      filters: [
        %Filter{field: :price, operator: :greater_than, value: 100, type: :integer},
        %Filter{field: :category, operator: :in, value: ["electronics", "gadgets"], type: :array}
      ],
      conjunction: :or
    }
  ],
  conjunction: :and
}

sorts = [
  %Sort{field: :priority, direction: :desc},
  %Sort{field: :created_at, direction: :asc}
]

query
|> QueryBuilder.build_query(filter_group)
|> QueryBuilder.apply_sort(sorts)

Summary

Functions

Applies sorting to a query.

Builds an Ecto query from a filter group.

Functions

apply_sort(query, sort)

@spec apply_sort(Ecto.Query.t(), LiveFilter.Sort.t() | [LiveFilter.Sort.t()] | nil) ::
  Ecto.Query.t()

Applies sorting to a query.

Accepts a single Sort struct, a list of Sort structs, or nil. When multiple sorts are provided, they are applied in order, creating a multi-column sort.

Parameters

  • query - The Ecto query to add sorting to
  • sorts - A Sort struct, list of Sort structs, or nil

Returns

The query with ORDER BY clauses applied, or the original query if no sorts.

Examples

# Single sort
apply_sort(query, %Sort{field: :due_date, direction: :asc})

# Multiple sorts (applied in order - priority desc, then due_date asc)
apply_sort(query, [
  %Sort{field: :priority, direction: :desc},
  %Sort{field: :due_date, direction: :asc}
])

# No sorting
apply_sort(query, nil)  # Returns original query unchanged

build_query(query, filter_group)

@spec build_query(Ecto.Query.t(), LiveFilter.FilterGroup.t()) :: Ecto.Query.t()

Builds an Ecto query from a filter group.

Converts a FilterGroup into dynamic Ecto query conditions using Ecto.Query.dynamic/2. Returns the original query unchanged if no filters are present.

Parameters

  • query - The base Ecto query to add conditions to
  • filter_group - A FilterGroup struct containing filters and nested groups

Returns

The query with WHERE conditions applied, or the original query if no filters.

Examples

iex> query = from p in Product
iex> filter_group = %FilterGroup{
...>   filters: [
...>     %Filter{field: :name, operator: :contains, value: "phone", type: :string}
...>   ]
...> }
iex> QueryBuilder.build_query(query, filter_group)
#Ecto.Query<from p0 in Product, where: ilike(p0.name, "%phone%")>

iex> empty_group = %FilterGroup{filters: []}
iex> QueryBuilder.build_query(query, empty_group) == query
true