Current section
Files
Jump to
Current section
Files
lib/aerospike/query.ex
defmodule Aerospike.Query do
@moduledoc """
Composable query description with separate predicate lanes.
The query itself is pure data. It does not own transport state or
partition iteration.
Query execution lives in explicit facade calls such as
`Aerospike.query_stream/3`, `Aerospike.query_aggregate/6`,
`Aerospike.query_aggregate_result/6`, `Aerospike.query_execute/4`, and
`Aerospike.query_udf/6`.
Node-targeted execution uses `node: node_name` in facade opts where that
query helper supports it. Discover valid names with
`Aerospike.node_names/1` or `Aerospike.nodes/1`. Finalized aggregate
queries do not support node targeting because they must consume every server
partial needed for one local final result. `query_all/3` and `query_page/3`
require `query.max_records`.
Use `where/2` for secondary-index predicates (`Aerospike.Filter`), and
`filter/2` for server-side expression filters (`Aerospike.Exp`). Both can be
used together where the query is supported.
"""
alias Aerospike.Exp
alias Aerospike.Filter
alias Aerospike.PartitionFilter
@typedoc "Bin names accepted by `select/2`."
@type bin_names :: [String.t()]
@enforce_keys [:namespace, :set]
defstruct [
:namespace,
:set,
:index_filter,
bin_names: [],
filters: [],
max_records: nil,
records_per_second: 0,
partition_filter: nil,
no_bins: false
]
@typedoc """
Query description consumed by `Aerospike.query_*` helpers.
`index_filter` is the single secondary-index query filter. `filters` are
server-side filter expressions combined with boolean AND by the encoder.
"""
@type t :: %__MODULE__{
namespace: String.t(),
set: String.t(),
index_filter: Filter.t() | nil,
bin_names: bin_names(),
filters: [Exp.t()],
max_records: pos_integer() | nil,
records_per_second: non_neg_integer(),
partition_filter: PartitionFilter.t() | nil,
no_bins: boolean()
}
@doc """
Starts a query for a namespace and set.
Both namespace and set must be non-empty strings.
"""
@spec new(String.t(), String.t()) :: t()
def new(namespace, set) when is_binary(namespace) and is_binary(set) do
validate_namespace!(namespace)
validate_set!(set)
%__MODULE__{namespace: namespace, set: set}
end
@doc """
Sets the secondary-index predicate.
A query can carry one secondary-index predicate. Calling `where/2` again
replaces the prior predicate.
"""
@spec where(t(), Filter.t()) :: t()
def where(%__MODULE__{} = query, %Filter{} = index_filter) do
%{query | index_filter: index_filter}
end
@doc """
Restricts returned bin names.
Passing an empty list keeps the server default of returning all bins unless
`no_bins/2` or execution opts request otherwise.
"""
@spec select(t(), bin_names()) :: t()
def select(%__MODULE__{} = query, bin_names) when is_list(bin_names) do
validate_bin_names!(bin_names)
%{query | bin_names: bin_names}
end
@doc """
Appends a server-side expression filter.
Multiple calls append additional expression filters that are combined with
server-side boolean AND when encoded.
"""
@spec filter(t(), Exp.t()) :: t()
def filter(%__MODULE__{} = query, %Exp{} = filter) do
%{query | filters: query.filters ++ [filter]}
end
@doc """
Sets the maximum number of records to return.
`query_all/3` and `query_page/3` require this value on the query struct.
The final result or page may contain fewer records when partition progress
completes first.
"""
@spec max_records(t(), pos_integer()) :: t()
def max_records(%__MODULE__{} = query, n) when is_integer(n) and n > 0 do
%{query | max_records: n}
end
@doc """
Sets the records-per-second throttle.
`0` disables query-level throttling.
"""
@spec records_per_second(t(), non_neg_integer()) :: t()
def records_per_second(%__MODULE__{} = query, n) when is_integer(n) and n >= 0 do
%{query | records_per_second: n}
end
@doc """
Attaches a partition filter for partial queries or advanced resume.
The filter is usually built with `Aerospike.PartitionFilter.all/0`,
`by_id/1`, `by_range/2`, or restored from a page cursor.
"""
@spec partition_filter(t(), PartitionFilter.t()) :: t()
def partition_filter(%__MODULE__{} = query, %PartitionFilter{} = partition_filter) do
%{query | partition_filter: partition_filter}
end
@doc """
When `true`, the server omits bin payloads.
This is useful for count-like queries or key/header-only workflows.
"""
@spec no_bins(t(), boolean()) :: t()
def no_bins(%__MODULE__{} = query, flag) when is_boolean(flag) do
%{query | no_bins: flag}
end
defp validate_namespace!(namespace) do
if namespace == "" do
raise ArgumentError, "namespace must be a non-empty string"
end
end
defp validate_set!(set) do
if set == "" do
raise ArgumentError, "set must be a non-empty string"
end
end
defp validate_bin_names!(bin_names) do
Enum.each(bin_names, fn name ->
unless is_binary(name) and name != "" do
raise ArgumentError, "bin names must be non-empty strings, got: #{inspect(name)}"
end
end)
end
end