Current section
Files
Jump to
Current section
Files
lib/aerospike/scan.ex
defmodule Aerospike.Scan do
@moduledoc """
Composable scan description.
The scan itself is pure data. It does not own transport state or
partition iteration.
Scan execution lives in explicit facade calls such as
`Aerospike.scan_stream/3`, `Aerospike.scan_all/3`, and
`Aerospike.scan_count/3`. Node-targeted execution uses `node: node_name`
in facade opts where supported; discover valid names with
`Aerospike.node_names/1` or `Aerospike.nodes/1`.
Filters are server-side expressions. Append expressions with
`filter/2` to keep returned records scoped by
`%Aerospike.Exp{}` evaluation.
"""
alias Aerospike.Exp
alias Aerospike.PartitionFilter
@typedoc "Bin names accepted by `select/2`."
@type bin_names :: [String.t()]
@enforce_keys [:namespace]
defstruct [
:namespace,
:set,
bin_names: [],
filters: [],
max_records: nil,
records_per_second: 0,
partition_filter: nil,
no_bins: false
]
@typedoc """
Scan description consumed by `Aerospike.scan_*` helpers.
`filters` are server-side filter expressions combined with boolean AND by
the encoder.
"""
@type t :: %__MODULE__{
namespace: String.t(),
set: String.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 namespace-wide scan.
The namespace must be a non-empty string. Use `new/2` to restrict the scan
to a set.
"""
@spec new(String.t()) :: t()
def new(namespace) when is_binary(namespace) do
validate_namespace!(namespace)
%__MODULE__{namespace: namespace, set: nil}
end
@doc """
Starts a scan limited to one 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 """
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__{} = scan, bin_names) when is_list(bin_names) do
validate_bin_names!(bin_names)
%{scan | bin_names: bin_names}
end
@doc """
Appends a server-side expression filter.
The scan encoder appends a single `FILTER_EXP` field for the expression
filter set.
"""
@spec filter(t(), Exp.t()) :: t()
def filter(%__MODULE__{} = scan, %Exp{} = filter) do
%{scan | filters: scan.filters ++ [filter]}
end
@doc """
Sets the maximum number of records to return.
Pagination helpers require `max_records` on the scan struct; the returned
page may contain fewer records when partition progress completes first.
"""
@spec max_records(t(), pos_integer()) :: t()
def max_records(%__MODULE__{} = scan, n) when is_integer(n) and n > 0 do
%{scan | max_records: n}
end
@doc """
Sets the records-per-second throttle.
`0` disables scan-level throttling.
"""
@spec records_per_second(t(), non_neg_integer()) :: t()
def records_per_second(%__MODULE__{} = scan, n) when is_integer(n) and n >= 0 do
%{scan | records_per_second: n}
end
@doc """
Attaches a partition filter for partial scans 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__{} = scan, %PartitionFilter{} = partition_filter) do
%{scan | partition_filter: partition_filter}
end
@doc """
When `true`, the server omits bin payloads.
This is useful for count-like scans or key/header-only workflows.
"""
@spec no_bins(t(), boolean()) :: t()
def no_bins(%__MODULE__{} = scan, flag) when is_boolean(flag) do
%{scan | 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