Skip to content

Commit 3797c2e

Browse files
committed
Make #kevf_*{} records private
The public API is now provided by the new `khepri_evf` module to construct the event filters. Before: EventFilter = #kevf_tree{path = Path, props = Props}. Now: EventFilter = khepri_evf:tree(Path, Props). The reason is the same as for the payload records: if we change the event filter records, users are not forced to recompile their code. The `khepri_machine:register_trigger/5` now tries to autodetect and convert common types to event filter, thanks to `khepri_evf:wrap/1`. This is the case for native and Unix-like paths which are converted to tree event filters. This allows users to skip the call to `khepri_evf` and make it easier to use the API with a path directly.
1 parent 8795b1c commit 3797c2e

File tree

9 files changed

+197
-53
lines changed

9 files changed

+197
-53
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ the database itself and automatically execute it after some event occurs.
178178
2. Register a trigger using an event filter:
179179

180180
```erlang
181-
EventFilter = #kevf_tree{path = [stock, wood, <<"oak">>]},
181+
%% A path is automatically considered a tree event filter.
182+
EventFilter = [stock, wood, <<"oak">>],
182183

183184
ok = khepri:register_trigger(
184185
StoreId,

doc/overview.edoc

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -406,14 +406,21 @@ arity.
406406

407407
Khepri uses <em>event filters</em> to associate a type of events with a stored
408408
procedure. Khepri supports tree changes events and thus only supports a single
409-
event filter called {@link khepri:event_filter_tree()}.
409+
event filter called {@link khepri_evf:tree_event_filter()}.
410410

411411
An event filter is registered using {@link khepri:register_trigger/4}:
412412

413413
```
414-
EventFilter = #kevf_tree{path = [stock, wood, <<"oak">>], %% Required
415-
props = #{on_actions => [delete], %% Optional
416-
priority => 10}}, %% Optional
414+
%% An event filter can be explicitely created using the `khepri_evf'
415+
%% module. This is possible to specify properties at the same time.
416+
EventFilter = khepri_evf:tree([stock, wood, <<"oak">>], %% Required
417+
#{on_actions => [delete], %% Optional
418+
priority => 10}), %% Optional
419+
420+
%% For ease of use, some terms can be automatically converted to an event %
421+
%filter. Here, a Unix-like path could be used as a tree event % filter, though
422+
%it would have default properties unlike the previous line:
423+
EventFilter = "/:stock/:wood/oak".
417424

418425
ok = khepri:register_trigger(
419426
StoreId,
@@ -422,7 +429,7 @@ ok = khepri:register_trigger(
422429
StoredProcPath))}.
423430
'''
424431

425-
In this example, the {@link khepri:event_filter_tree()} record only
432+
In this example, the {@link khepri_evf:tree_event_filter()} structure only
426433
requires the path to monitor. The path can be any path pattern and thus can
427434
have conditions to monitor several nodes at once.
428435

include/khepri.hrl

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,3 @@
8383

8484
-record(if_any,
8585
{conditions = [] :: [khepri_path:pattern_component()]}).
86-
87-
%% -------------------------------------------------------------------
88-
%% Event filtering.
89-
%% -------------------------------------------------------------------
90-
91-
-record(kevf_tree, {path :: khepri_path:pattern(),
92-
props = #{} :: #{on_actions => [create | update | delete],
93-
priority => integer()}}).
94-
%-record(kevf_process, {pid :: pid(),
95-
% props = #{} :: #{on_reason => ets:match_pattern(),
96-
% priority => integer()}}).
97-
98-
-define(IS_KHEPRI_EVENT_FILTER(EventFilter),
99-
(is_record(EventFilter, kevf_tree))).

src/internal.hrl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@
2424
is_record(Payload, p_data) orelse
2525
is_record(Payload, p_sproc))).
2626

27+
-record(evf_tree, {path :: khepri_path:pattern(),
28+
props = #{} :: khepri_evf:tree_event_filter_props()}).
29+
%-record(evf_process, {pid :: pid(),
30+
% props = #{} :: #{on_reason => ets:match_pattern(),
31+
% priority => integer()}}).
32+
33+
-define(IS_KHEPRI_EVENT_FILTER(EventFilter),
34+
(is_record(EventFilter, evf_tree))).
35+
2736
%% Structure representing each node in the tree, including the root node.
2837
%% TODO: Rename stat to something more correct?
2938
-record(node, {stat = ?INIT_NODE_STAT :: khepri_machine:stat(),
@@ -42,14 +51,14 @@
4251
-record(tx, {'fun' :: khepri_fun:standalone_fun()}).
4352

4453
-record(register_trigger, {id :: khepri:trigger_id(),
45-
event_filter :: khepri:event_filter(),
54+
event_filter :: khepri_evf:event_filter(),
4655
sproc :: khepri_path:path()}).
4756

4857
-record(ack_triggered, {triggered :: [khepri_machine:triggered()]}).
4958

5059
-record(triggered, {id :: khepri:trigger_id(),
5160
%% TODO: Do we need a ref to distinguish multiple
5261
%% instances of the same trigger?
53-
event_filter :: khepri:event_filter(),
62+
event_filter :: khepri_evf:event_filter(),
5463
sproc :: khepri_fun:standalone_fun(),
5564
props = #{} :: map()}).

src/khepri.erl

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,6 @@
215215
-type trigger_id() :: atom().
216216
%% An ID to identify a registered trigger.
217217

218-
-type event_filter_tree() :: #kevf_tree{}.
219-
220-
-type event_filter() :: event_filter_tree().
221-
222218
-type async_option() :: boolean() |
223219
ra_server:command_correlation() |
224220
ra_server:command_priority() |
@@ -305,8 +301,6 @@
305301
result/0,
306302
keep_while_conds_map/0,
307303
trigger_id/0,
308-
event_filter_tree/0,
309-
event_filter/0,
310304

311305
async_option/0,
312306
favor_option/0,
@@ -1501,7 +1495,8 @@ run_sproc(StoreId, PathPattern, Args, Options) ->
15011495

15021496
-spec register_trigger(TriggerId, EventFilter, StoredProcPath) -> Ret when
15031497
TriggerId :: trigger_id(),
1504-
EventFilter :: event_filter(),
1498+
EventFilter :: khepri_evf:event_filter() |
1499+
khepri_path:pattern() | string(),
15051500
StoredProcPath :: khepri_path:path() | string(),
15061501
Ret :: ok | error().
15071502
%% @doc Registers a trigger.
@@ -1519,12 +1514,14 @@ register_trigger(TriggerId, EventFilter, StoredProcPath) ->
15191514
(StoreId, TriggerId, EventFilter, StoredProcPath) -> Ret when
15201515
StoreId :: khepri:store_id(),
15211516
TriggerId :: trigger_id(),
1522-
EventFilter :: event_filter(),
1517+
EventFilter :: khepri_evf:event_filter() |
1518+
khepri_path:pattern() | string(),
15231519
StoredProcPath :: khepri_path:path() | string(),
15241520
Ret :: ok | error();
15251521
(TriggerId, EventFilter, StoredProcPath, Options) -> Ret when
15261522
TriggerId :: trigger_id(),
1527-
EventFilter :: event_filter(),
1523+
EventFilter :: khepri_evf:event_filter() |
1524+
khepri_path:pattern() | string(),
15281525
StoredProcPath :: khepri_path:path() | string(),
15291526
Options :: command_options(),
15301527
Ret :: ok | error().
@@ -1556,20 +1553,34 @@ register_trigger(TriggerId, EventFilter, StoredProcPath, Options)
15561553
Ret when
15571554
StoreId :: khepri:store_id(),
15581555
TriggerId :: trigger_id(),
1559-
EventFilter :: event_filter(),
1556+
EventFilter :: khepri_evf:event_filter() |
1557+
khepri_path:pattern() | string(),
15601558
StoredProcPath :: khepri_path:path() | string(),
15611559
Options :: command_options(),
15621560
Ret :: ok | error().
15631561
%% @doc Registers a trigger.
15641562
%%
15651563
%% A trigger is based on an event filter. It associates an event with a stored
15661564
%% procedure. When an event matching the event filter is emitted, the stored
1567-
%% procedure is executed. Here is an example of an event filter:
1565+
%% procedure is executed.
1566+
%%
1567+
%% The following event filters are documented by {@link
1568+
%% khepri_evf:event_filter()}.
15681569
%%
1570+
%% Here are examples of event filters:
1571+
%%
1572+
%% ```
1573+
%% %% An event filter can be explicitely created using the `khepri_evf'
1574+
%% %% module. This is possible to specify properties at the same time.
1575+
%% EventFilter = khepri_evf:tree([stock, wood, <<"oak">>], %% Required
1576+
%% #{on_actions => [delete], %% Optional
1577+
%% priority => 10}). %% Optional
1578+
%% '''
15691579
%% ```
1570-
%% EventFilter = #kevf_tree{path = [stock, wood, <<"oak">>], %% Required
1571-
%% props = #{on_actions => [delete], %% Optional
1572-
%% priority => 10}}, %% Optional
1580+
%% %% For ease of use, some terms can be automatically converted to an event
1581+
%% %% filter. In this example, a Unix-like path can be used as a tree event
1582+
%% %% filter.
1583+
%% EventFilter = "/:stock/:wood/oak".
15731584
%% '''
15741585
%%
15751586
%% The stored procedure is expected to accept a single argument. This argument

src/khepri_evf.erl

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
%% This Source Code Form is subject to the terms of the Mozilla Public
2+
%% License, v. 2.0. If a copy of the MPL was not distributed with this
3+
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
%%
5+
%% Copyright (c) 2021-2022 VMware, Inc. or its affiliates. All rights reserved.
6+
%%
7+
8+
%% @doc Khepri event filters.
9+
10+
-module(khepri_evf).
11+
12+
-include("src/internal.hrl").
13+
14+
-export([tree/1, tree/2,
15+
wrap/1,
16+
get_priority/1,
17+
set_priority/2]).
18+
19+
-type tree_event_filter() :: #evf_tree{}.
20+
%% A tree event filter.
21+
%%
22+
%% It takes a path pattern to monitor and optionally properties.
23+
24+
-type tree_event_filter_props() :: #{on_actions => [create | update | delete],
25+
priority => khepri_evf:priority()}.
26+
%% Tree event filter properties.
27+
%%
28+
%% The properties are:
29+
%% <ul>
30+
%% <li>`on_actions': a list of actions to filter among `create', `update' and
31+
%% `delete'; the default is to react to all of them.</li>
32+
%% <li>`priority': a {@link priority()}</li>
33+
%% </ul>
34+
%%
35+
%% A Khepri path, wether it is a native path or a Unix-like path, can be used
36+
%% as a tree event filter. It will be automatically converted to a tree event
37+
%% filter with default properties.
38+
39+
-type event_filter() :: tree_event_filter().
40+
%% An event filter.
41+
%%
42+
%% The following event filters are supported:
43+
%% <ul>
44+
%% <li>Tree event filter ({@link tree_event_filter()}</li>
45+
%% </ul>
46+
%%
47+
%% An event filter can be explicitly constructed using the functions provided
48+
%% in this module. However, some common types will be automatically detected
49+
%% and converted to an event filter with default properties. See each event
50+
%% filter type for more details.
51+
52+
-type priority() :: integer().
53+
%% An event filter priority.
54+
%%
55+
%% This is an integer to prioritize event filters: the greater the priority,
56+
%% the more it is prioritized. Negative integers are allowed.
57+
%%
58+
%% The default priority is 0.
59+
60+
-export_type([event_filter/0,
61+
tree_event_filter/0,
62+
tree_event_filter_props/0,
63+
priority/0]).
64+
65+
-spec tree(PathPattern) -> EventFilter when
66+
PathPattern :: khepri_path:pattern() | string(),
67+
EventFilter :: tree_event_filter().
68+
%% @doc Constructs a tree event filter.
69+
%%
70+
%% @see tree/2.
71+
72+
tree(PathPattern) ->
73+
tree(PathPattern, #{}).
74+
75+
-spec tree(PathPattern, Props) -> EventFilter when
76+
PathPattern :: khepri_path:pattern() | string(),
77+
Props :: tree_event_filter_props(),
78+
EventFilter :: tree_event_filter().
79+
%% @doc Constructs a tree event filter.
80+
%%
81+
%% @see tree_event_filter().
82+
83+
tree(PathPattern, Props) ->
84+
PathPattern1 = khepri_path:from_string(PathPattern),
85+
#evf_tree{path = PathPattern1,
86+
props = Props}.
87+
88+
-spec wrap(Input) -> EventFilter when
89+
Input :: event_filter() | khepri_path:pattern() | string(),
90+
EventFilter :: event_filter().
91+
%% @doc Automatically detects the event filter type and ensures it is wrapped
92+
%% in one of the internal types.
93+
%%
94+
%% @param Input an already created event filter, or any term which can be
95+
%% automatically converted to an event filter.
96+
%%
97+
%% @returns the created event filter.
98+
99+
wrap(EventFilter) when ?IS_KHEPRI_EVENT_FILTER(EventFilter) ->
100+
EventFilter;
101+
wrap(PathPattern) when is_list(PathPattern) ->
102+
tree(PathPattern).
103+
104+
-spec get_priority(EventFilter) -> Priority when
105+
EventFilter :: event_filter(),
106+
Priority :: priority().
107+
%% @doc Returns the priority of the event filter.
108+
%%
109+
%% @param EventFilter the event filter to update.
110+
%%
111+
%% @returns the priority.
112+
113+
get_priority(#evf_tree{props = Props}) ->
114+
get_priority1(Props).
115+
116+
get_priority1(#{priority := Priority}) -> Priority;
117+
get_priority1(_) -> 0.
118+
119+
-spec set_priority(EventFilter, Priority) -> EventFilter when
120+
EventFilter :: event_filter(),
121+
Priority :: priority().
122+
%% @doc Sets the priority of the event filter.
123+
%%
124+
%% @param EventFilter the event filter to update.
125+
%% @param Priority the new priority.
126+
%%
127+
%% @returns the updated event filter.
128+
129+
set_priority(#evf_tree{props = Props} = EventFilter, Priority) ->
130+
Props1 = set_priority1(Props, Priority),
131+
EventFilter#evf_tree{props = Props1}.
132+
133+
set_priority1(Props, Priority) when is_integer(Priority) ->
134+
Props#{priority => Priority}.

src/khepri_machine.erl

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ run_sproc(StoreId, PathPattern, Args, Options) when is_list(Args) ->
353353
Ret when
354354
StoreId :: khepri:store_id(),
355355
TriggerId :: khepri:trigger_id(),
356-
EventFilter :: khepri:event_filter(),
356+
EventFilter :: khepri_evf:event_filter() |
357+
khepri_path:pattern() | string(),
357358
StoredProcPath :: khepri_path:path() | string(),
358359
Options :: khepri:command_options(),
359360
Ret :: ok | khepri:error().
@@ -369,13 +370,13 @@ run_sproc(StoreId, PathPattern, Args, Options) when is_list(Args) ->
369370
%% @returns `ok' if the trigger was registered, an `{error, Reason}' tuple
370371
%% otherwise.
371372

372-
register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath, Options)
373-
when ?IS_KHEPRI_EVENT_FILTER(EventFilter) ->
373+
register_trigger(StoreId, TriggerId, EventFilter, StoredProcPath, Options) ->
374+
EventFilter1 = khepri_evf:wrap(EventFilter),
374375
StoredProcPath1 = khepri_path:from_string(StoredProcPath),
375376
khepri_path:ensure_is_valid(StoredProcPath1),
376377
Command = #register_trigger{id = TriggerId,
377378
sproc = StoredProcPath1,
378-
event_filter = EventFilter},
379+
event_filter = EventFilter1},
379380
process_command(StoreId, Command, Options).
380381

381382
-spec ack_triggers_execution(StoreId, TriggeredStoredProcs) ->
@@ -803,9 +804,9 @@ apply(
803804
#?MODULE{triggers = Triggers} = State) ->
804805
StoredProcPath1 = khepri_path:realpath(StoredProcPath),
805806
EventFilter1 = case EventFilter of
806-
#kevf_tree{path = Path} ->
807+
#evf_tree{path = Path} ->
807808
Path1 = khepri_path:realpath(Path),
808-
EventFilter#kevf_tree{path = Path1}
809+
EventFilter#evf_tree{path = Path1}
809810
end,
810811
Triggers1 = Triggers#{TriggerId => #{sproc => StoredProcPath1,
811812
event_filter => EventFilter1}},
@@ -1367,7 +1368,7 @@ list_triggered_sprocs(Root, Changes, Triggers) ->
13671368
(TriggerId,
13681369
#{sproc := StoredProcPath,
13691370
event_filter :=
1370-
#kevf_tree{path = PathPattern,
1371+
#evf_tree{path = PathPattern,
13711372
props = EventFilterProps} = EventFilter},
13721373
SPP1) ->
13731374
%% For each trigger based on a tree event:
@@ -1482,21 +1483,15 @@ sort_triggered_sprocs(TriggeredStoredProcs) ->
14821483
lists:sort(
14831484
fun(#triggered{id = IdA, event_filter = EventFilterA},
14841485
#triggered{id = IdB, event_filter = EventFilterB}) ->
1485-
PrioA = get_event_filter_priority(EventFilterA),
1486-
PrioB = get_event_filter_priority(EventFilterB),
1486+
PrioA = khepri_evf:get_priority(EventFilterA),
1487+
PrioB = khepri_evf:get_priority(EventFilterB),
14871488
if
14881489
PrioA =:= PrioB -> IdA =< IdB;
14891490
true -> PrioA > PrioB
14901491
end
14911492
end,
14921493
TriggeredStoredProcs).
14931494

1494-
get_event_filter_priority(#kevf_tree{props = #{priority := Priority}})
1495-
when is_integer(Priority) ->
1496-
Priority;
1497-
get_event_filter_priority(_EventFilter) ->
1498-
0.
1499-
15001495
%% -------
15011496

15021497
-spec walk_down_the_tree(

src/khepri_machine.hrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
triggers = #{} ::
2222
#{khepri:trigger_id() =>
2323
#{sproc := khepri_path:path(),
24-
event_filter := khepri:event_filter()}},
24+
event_filter := khepri_evf:event_filter()}},
2525
emitted_triggers = [] :: [khepri_machine:triggered()],
2626
metrics = #{} :: #{applied_command_count => non_neg_integer()}}).

0 commit comments

Comments
 (0)