Current section
Files
Jump to
Current section
Files
src/erlfdb_iterator.erl
-module(erlfdb_iterator).
-define(DOCATTRS, ?OTP_RELEASE >= 27).
-if(?DOCATTRS).
-moduledoc """
A generic iterator behaviour, useful for streaming results from the database.
""".
-endif.
-type state() :: term().
-type iterator() :: {module(), state()}.
-type result() :: term().
-callback handle_call(term(), term(), state()) -> {reply, term(), state()}.
-callback handle_next(state()) ->
{cont, [result()], state()} | {halt, [result()], state()} | {halt, state()}.
-callback handle_stop(state()) -> ok.
-optional_callbacks([handle_call/3]).
-export_type([iterator/0, result/0]).
-export([new/2, next/1, stop/1, run/1, call/2, pipeline/1, module_state/1]).
-if(?DOCATTRS).
-doc """
Creates an iterator.
""".
-endif.
-spec new(module(), state()) -> iterator().
new(Module, State) ->
{Module, State}.
-if(?DOCATTRS).
-doc """
Executes a handle_call on the iterator.
""".
-endif.
-spec call(iterator(), term()) -> {term(), iterator()}.
call(Iterator, Call) ->
{Module, State} = module_state(Iterator),
{reply, Reply, State1} = Module:handle_call(Call, make_ref(), State),
{Reply, new(Module, State1)}.
-if(?DOCATTRS).
-doc """
Progresses the iterator one step.
""".
-endif.
-spec next(iterator()) ->
{cont, [result()], iterator()} | {halt, [result()], iterator()} | {halt, iterator()}.
next({Module, State}) ->
case Module:handle_next(State) of
{cont, Results, State1} ->
{cont, Results, {Module, State1}};
{halt, Results, State1} ->
{halt, Results, {Module, State1}};
{halt, State1} ->
{halt, {Module, State1}}
end.
-if(?DOCATTRS).
-doc """
Stops the iterator.
""".
-endif.
-spec stop(iterator()) -> ok.
stop({Module, State}) ->
ok = Module:handle_stop(State).
-if(?DOCATTRS).
-doc """
Runs the iterator to completion.
Returns the list of results and the state of the iterator.
Caller must call `stop/1` to terminate the iterator.
""".
-endif.
-spec run(iterator()) -> {list(result()), iterator()}.
run(Iterator) ->
[{Result, Iterator1}] = pipeline([Iterator]),
{Result, Iterator1}.
-if(?DOCATTRS).
-doc """
Runs all iterators to completion.
Caller must call `stop/1` to terminate the iterators.
""".
-endif.
-spec pipeline([iterator()]) -> [{list(result()), iterator()}].
pipeline(List) ->
Len = length(List),
Acc = erlang:make_tuple(Len, []),
IxList = lists:zip(lists:seq(1, Len), List),
pipeline(IxList, Acc).
-if(?DOCATTRS).
-doc """
Identifies the module and the specific state of the iterator implementation.
""".
-endif.
-spec module_state(iterator()) -> {module(), state()}.
module_state({Module, State}) -> {Module, State}.
pipeline([], Acc) ->
tuple_to_list(Acc);
pipeline(IxList, Acc) ->
{Remaining, Acc1} = lists:foldl(
fun({Ix, Iterator}, {Rem0, Acc0}) ->
case next(Iterator) of
{halt, Iterator1} ->
ok = stop(Iterator1),
AccResults = lists:append(lists:reverse(erlang:element(Ix, Acc0))),
{Rem0, erlang:setelement(Ix, Acc0, {AccResults, Iterator1})};
{halt, [], Iterator1} ->
ok = stop(Iterator1),
AccResults = lists:append(lists:reverse(erlang:element(Ix, Acc0))),
{Rem0, erlang:setelement(Ix, Acc0, {AccResults, Iterator1})};
{halt, Results, Iterator1} ->
ok = stop(Iterator1),
AccResults = lists:append(lists:reverse([Results | erlang:element(Ix, Acc0)])),
{Rem0, erlang:setelement(Ix, Acc0, {AccResults, Iterator1})};
{cont, [], Iterator1} ->
{[{Ix, Iterator1} | Rem0], Acc0};
{cont, Results, Iterator1} ->
AccResults = erlang:element(Ix, Acc0),
{[{Ix, Iterator1} | Rem0], erlang:setelement(Ix, Acc0, [Results | AccResults])}
end
end,
{[], Acc},
IxList
),
pipeline(lists:reverse(Remaining), Acc1).