|
| 1 | +-module(erlfdb_iterator). |
| 2 | + |
| 3 | +-define(DOCATTRS, ?OTP_RELEASE >= 27). |
| 4 | + |
| 5 | +-if(?DOCATTRS). |
| 6 | +-moduledoc """ |
| 7 | +A generic iterator behaviour, useful for streaming results from the database. |
| 8 | +""". |
| 9 | +-endif. |
| 10 | + |
| 11 | +-type state() :: term(). |
| 12 | +-type iterator() :: {module(), state()}. |
| 13 | +-type result() :: term(). |
| 14 | + |
| 15 | +-callback handle_call(term(), term(), state()) -> {reply, term(), state()}. |
| 16 | +-callback handle_next(state()) -> |
| 17 | + {cont, [result()], state()} | {halt, [result()], state()} | {halt, state()}. |
| 18 | +-callback handle_stop(state()) -> ok. |
| 19 | + |
| 20 | +-optional_callbacks([handle_call/3]). |
| 21 | + |
| 22 | +-export_type([iterator/0, result/0]). |
| 23 | + |
| 24 | +-export([new/2, next/1, stop/1, run/1, call/2, pipeline/1, module_state/1]). |
| 25 | + |
| 26 | +-if(?DOCATTRS). |
| 27 | +-doc """ |
| 28 | +Creates an iterator. |
| 29 | +""". |
| 30 | +-endif. |
| 31 | +-spec new(module(), state()) -> iterator(). |
| 32 | +new(Module, State) -> |
| 33 | + {Module, State}. |
| 34 | + |
| 35 | +-if(?DOCATTRS). |
| 36 | +-doc """ |
| 37 | +Executes a handle_call on the iterator. |
| 38 | +""". |
| 39 | +-endif. |
| 40 | +-spec call(iterator(), term()) -> {term(), iterator()}. |
| 41 | +call(Iterator, Call) -> |
| 42 | + {Module, State} = module_state(Iterator), |
| 43 | + {reply, Reply, State1} = Module:handle_call(Call, make_ref(), State), |
| 44 | + {Reply, new(Module, State1)}. |
| 45 | + |
| 46 | +-if(?DOCATTRS). |
| 47 | +-doc """ |
| 48 | +Progresses the iterator one step. |
| 49 | +""". |
| 50 | +-endif. |
| 51 | +-spec next(iterator()) -> |
| 52 | + {cont, [result()], iterator()} | {halt, [result()], iterator()} | {halt, iterator()}. |
| 53 | +next({Module, State}) -> |
| 54 | + case Module:handle_next(State) of |
| 55 | + {cont, Results, State1} -> |
| 56 | + {cont, Results, {Module, State1}}; |
| 57 | + {halt, Results, State1} -> |
| 58 | + {halt, Results, {Module, State1}}; |
| 59 | + {halt, State1} -> |
| 60 | + {halt, {Module, State1}} |
| 61 | + end. |
| 62 | + |
| 63 | +-if(?DOCATTRS). |
| 64 | +-doc """ |
| 65 | +Stops the iterator. |
| 66 | +""". |
| 67 | +-endif. |
| 68 | +-spec stop(iterator()) -> ok. |
| 69 | +stop({Module, State}) -> |
| 70 | + ok = Module:handle_stop(State). |
| 71 | + |
| 72 | +-if(?DOCATTRS). |
| 73 | +-doc """ |
| 74 | +Runs the iterator to completion. |
| 75 | + |
| 76 | +Returns the list of results and the state of the iterator. |
| 77 | + |
| 78 | +Caller must call `stop/1` to terminate the iterator. |
| 79 | +""". |
| 80 | +-endif. |
| 81 | +-spec run(iterator()) -> {list(result()), iterator()}. |
| 82 | +run(Iterator) -> |
| 83 | + [{Result, Iterator1}] = pipeline([Iterator]), |
| 84 | + {Result, Iterator1}. |
| 85 | + |
| 86 | +-if(?DOCATTRS). |
| 87 | +-doc """ |
| 88 | +Runs all iterators to completion. |
| 89 | + |
| 90 | +Caller must call `stop/1` to terminate the iterators. |
| 91 | +""". |
| 92 | +-endif. |
| 93 | +-spec pipeline([iterator()]) -> [{list(result()), iterator()}]. |
| 94 | +pipeline(List) -> |
| 95 | + Len = length(List), |
| 96 | + Acc = erlang:make_tuple(Len, []), |
| 97 | + IxList = lists:zip(lists:seq(1, Len), List), |
| 98 | + pipeline(IxList, Acc). |
| 99 | + |
| 100 | +-if(?DOCATTRS). |
| 101 | +-doc """ |
| 102 | +Identifies the module and the specific state of the iterator implementation. |
| 103 | +""". |
| 104 | +-endif. |
| 105 | +-spec module_state(iterator()) -> {module(), state()}. |
| 106 | +module_state({Module, State}) -> {Module, State}. |
| 107 | + |
| 108 | +pipeline([], Acc) -> |
| 109 | + tuple_to_list(Acc); |
| 110 | +pipeline(IxList, Acc) -> |
| 111 | + {Remaining, Acc1} = lists:foldl( |
| 112 | + fun({Ix, Iterator}, {Rem0, Acc0}) -> |
| 113 | + case next(Iterator) of |
| 114 | + {halt, Iterator1} -> |
| 115 | + ok = stop(Iterator1), |
| 116 | + AccResults = lists:append(lists:reverse(erlang:element(Ix, Acc0))), |
| 117 | + {Rem0, erlang:setelement(Ix, Acc0, {AccResults, Iterator1})}; |
| 118 | + {halt, [], Iterator1} -> |
| 119 | + ok = stop(Iterator1), |
| 120 | + AccResults = lists:append(lists:reverse(erlang:element(Ix, Acc0))), |
| 121 | + {Rem0, erlang:setelement(Ix, Acc0, {AccResults, Iterator1})}; |
| 122 | + {halt, Results, Iterator1} -> |
| 123 | + ok = stop(Iterator1), |
| 124 | + AccResults = lists:append(lists:reverse([Results | erlang:element(Ix, Acc0)])), |
| 125 | + {Rem0, erlang:setelement(Ix, Acc0, {AccResults, Iterator1})}; |
| 126 | + {cont, [], Iterator1} -> |
| 127 | + {[{Ix, Iterator1} | Rem0], Acc0}; |
| 128 | + {cont, Results, Iterator1} -> |
| 129 | + AccResults = erlang:element(Ix, Acc0), |
| 130 | + {[{Ix, Iterator1} | Rem0], erlang:setelement(Ix, Acc0, [Results | AccResults])} |
| 131 | + end |
| 132 | + end, |
| 133 | + {[], Acc}, |
| 134 | + IxList |
| 135 | + ), |
| 136 | + pipeline(lists:reverse(Remaining), Acc1). |
0 commit comments