|
27 | 27 | %% Updates to projection tables are immediately consistent for the member of |
28 | 28 | %% the cluster on which the change to the store is made and the leader member |
29 | 29 | %% but are eventually consistent for all other followers. |
| 30 | +%% |
| 31 | +%% == Projection functions == |
| 32 | +%% |
| 33 | +%% There are three supported types of projection functions: |
| 34 | +%% <ol> |
| 35 | +%% <li>`copy' (the literal atom)</li> |
| 36 | +%% <li>the simple projection function (identified by its arity of 2)</li> |
| 37 | +%% <li>the extended projection function (identified by its arity of 4)</li> |
| 38 | +%% </ol> |
| 39 | +%% |
| 40 | +%% === copy === |
| 41 | +%% |
| 42 | +%% This projection "function" inserts the value of a tree node matching the |
| 43 | +%% projection path pattern to the ETS table. The value is expected to be a |
| 44 | +%% tuple or a record that can be inserted in the table. |
| 45 | +%% |
| 46 | +%% If the tree node was deleted, the old value is deleted from the ETS table. |
| 47 | +%% |
| 48 | +%% This is the most efficient projection function as it doesn't rely on an |
| 49 | +%% anonymous function. |
| 50 | +%% |
| 51 | +%% === Simple projection function === |
| 52 | +%% |
| 53 | +%% This projection function is called to convert the value of a tree node |
| 54 | +%% matching the projection path pattern to some arbitrary term. If the tree |
| 55 | +%% node is created or updated, the value passed to the projection function is |
| 56 | +%% the new value after the creation/update. If the tree node is deleted, the |
| 57 | +%% value is the one of the tree node before the deletion. The result of the |
| 58 | +%% projection function is then inserted in or deleted from the ETS table. |
| 59 | +%% |
| 60 | +%% The projection function is expected to map one path/value to one ETS entry. |
| 61 | +%% There is no way for the projection function to indicate that a path/value |
| 62 | +%% should not be intserted in ETS for instance. |
| 63 | +%% |
| 64 | +%% The simple projection function takes 2 arguments: |
| 65 | +%% <ol> |
| 66 | +%% <li>the tree node path</li> |
| 67 | +%% <li>the tree node value</li> |
| 68 | +%% </ol> |
| 69 | +%% |
| 70 | +%% Example: |
| 71 | +%% |
| 72 | +%% ``` |
| 73 | +%% ProjectionName = wood_stocks, |
| 74 | +%% ProjectionFun = fun([stock, wood, Kind] = _Path, Stock) -> |
| 75 | +%% {Kind, Stock} |
| 76 | +%% end, |
| 77 | +%% Options = #{type => set, |
| 78 | +%% read_concurrency => true}, |
| 79 | +%% Projection = khepri_projection:new(ProjectionName, ProjectionFun, Options). |
| 80 | +%% ''' |
| 81 | +%% |
| 82 | +%% The resulting ETS will look like this: |
| 83 | +%% |
| 84 | +%% ``` |
| 85 | +%% [ |
| 86 | +%% {<<"oak">>, 100}, |
| 87 | +%% {<<"maple">>, 180} |
| 88 | +%% ] = ets:tab2list(wood_stoks). |
| 89 | +%% ''' |
| 90 | +%% |
| 91 | +%% === Extended projection function === |
| 92 | +%% |
| 93 | +%% This projection function is responsible for managing the content of the ETS |
| 94 | +%% table(s), even though the ETS tables are still created by Khepri. |
| 95 | +%% |
| 96 | +%% This is useful when a tree node value can be mapped to several entries in an |
| 97 | +%% ETS table or it can be mapped to entries in several ETS tables. |
| 98 | +%% |
| 99 | +%% By default, if the projection is created like a simple projection function, |
| 100 | +%% a single ETS table is created, named after the projection. To use several |
| 101 | +%% ETS tables, the caller has to specify the list of ETS tables and their |
| 102 | +%% per-table ETS options. |
| 103 | +%% |
| 104 | +%% The simple projection function takes 4 arguments: |
| 105 | +%% <ol> |
| 106 | +%% <li>the table ID, or the map of table names/IDs if multiple tables where configured</li> |
| 107 | +%% <li>the tree node path</li> |
| 108 | +%% <li>the tree node properies before the update/deletion</li> |
| 109 | +%% <li>the tree node properies after the update/deletion</li> |
| 110 | +%% </ol> |
| 111 | +%% |
| 112 | +%% Example: |
| 113 | +%% |
| 114 | +%% This extended projection function reproduces the behaviour of the simple |
| 115 | +%% projection function from the example above basically. |
| 116 | +%% |
| 117 | +%% ``` |
| 118 | +%% ProjectionName = wood_stocks, |
| 119 | +%% ProjectionFun = fun |
| 120 | +%% %% Stock update. |
| 121 | +%% (Tid, [stock, wood, Kind], _OldProps, #{data := Stock}) -> |
| 122 | +%% ets:insert(Tid, {Kind, Stock}); |
| 123 | +%% |
| 124 | +%% %% Stock deletion. |
| 125 | +%% (Tid, [stock, wood, Kind], #{data := Stock}, _NewProps) -> |
| 126 | +%% ets:delete(Tid, {Kind, Stock}) |
| 127 | +%% end, |
| 128 | +%% Options = #{type => set, |
| 129 | +%% read_concurrency => true}, |
| 130 | +%% Projection = khepri_projection:new(ProjectionName, ProjectionFun, Options). |
| 131 | +%% ''' |
| 132 | +%% |
| 133 | +%% The resulting ETS will look like this: |
| 134 | +%% |
| 135 | +%% ``` |
| 136 | +%% [ |
| 137 | +%% {<<"oak">>, 100}, |
| 138 | +%% {<<"maple">>, 180} |
| 139 | +%% ] = ets:tab2list(wood_stoks). |
| 140 | +%% ''' |
| 141 | +%% |
| 142 | +%% A projection function becomes useful when there are more involved mapping of |
| 143 | +%% ETS entries and possibly multiple ETS tables to manage: |
| 144 | +%% |
| 145 | +%% ``` |
| 146 | +%% ProjectionName = wood_stocks, |
| 147 | +%% ProjectionFun = fun |
| 148 | +%% %% Stock update. |
| 149 | +%% (#{wood_stocks := WoodStocksTid, |
| 150 | +%% wood_needs := WoodNeedsTid}, |
| 151 | +%% [stock, wood, Kind], |
| 152 | +%% _OldProps, |
| 153 | +%% #{data := Stock}) -> |
| 154 | +%% ets:insert(WoodStocksTid, {Kind, Stock}), |
| 155 | +%% |
| 156 | +%% %% Depending on the stock, we check if we need to order |
| 157 | +%% %% new wood. |
| 158 | +%% if |
| 159 | +%% Stock < 50 -> |
| 160 | +%% ets:insert(WoodNeedsTid, {Kind, true}); |
| 161 | +%% Stock > 1000 -> |
| 162 | +%% ets:delete(WoodNeedsTid, {Kind, true}); |
| 163 | +%% true -> |
| 164 | +%% ok |
| 165 | +%% end; |
| 166 | +%% |
| 167 | +%% %% Stock deletion. |
| 168 | +%% (#{wood_stocks := WoodStocksTid, |
| 169 | +%% wood_needs := WoodNeedsTid}, |
| 170 | +%% [stock, wood, Kind], |
| 171 | +%% #{data := Stock}, |
| 172 | +%% _NewProps) -> |
| 173 | +%% ets:delete(WoodStocksTid, {Kind, Stock}), |
| 174 | +%% %% We definitely need to order wood of this kind. |
| 175 | +%% ets:insert(WoodNeedsTid, {Kind, true}); |
| 176 | +%% end, |
| 177 | +%% Options = #{%% Map of ETS tables and their specific ETS options; here, we don't |
| 178 | +%% %% need specific ETS options: they will use the globablly defined |
| 179 | +%% %% options as a fallback. |
| 180 | +%% tables => #{wood_stocks => #{}, |
| 181 | +%% wood_needs => #{}}, |
| 182 | +%% |
| 183 | +%% %% Global ETS options used for tables that do not override them. |
| 184 | +%% type => set, |
| 185 | +%% read_concurrency => true}, |
| 186 | +%% Projection = khepri_projection:new(ProjectionName, ProjectionFun, Options). |
| 187 | +%% ''' |
30 | 188 |
|
31 | 189 | -module(khepri_projection). |
32 | 190 |
|
|
0 commit comments