From 2a8e7a0e76301e4fca04d86cb296dfff1928034c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20P=C3=A9dron?= Date: Wed, 5 Nov 2025 21:38:28 +0100 Subject: [PATCH] khepri_tree: Ignore `keep_while` conditions on children on node creation [Why] If a parent has a `keep_while` condition on a child node, the child node can't possibly exist when the parent is created. Therefore, ignore this condition when the parent is created because it will always be false. [How] Drop `keep_while` conditions on nodes that have the created node as prefix. While here, move this logic to its own function. --- src/khepri_tree.erl | 26 ++++++++++++++++++++++---- test/keep_while_conditions.erl | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/khepri_tree.erl b/src/khepri_tree.erl index d4bb6e89..01b37bd6 100644 --- a/src/khepri_tree.erl +++ b/src/khepri_tree.erl @@ -655,11 +655,13 @@ insert_or_update_node( Path, Node, Payload, TreeOptions, Result), case Ret of {ok, Node1, Result1} when Result1 =/= #{} -> - AbsKeepWhile = to_absolute_keep_while( - Path, KeepWhile), - KeepWhileOnOthers = maps:remove(Path, AbsKeepWhile), + AbsKeepWhile0 = to_absolute_keep_while( + Path, KeepWhile), + AbsKeepWhile1 = ( + filter_out_irrelevant_keep_while_conds( + Path, Node, AbsKeepWhile0)), KWMet = are_keep_while_conditions_met( - Tree, KeepWhileOnOthers), + Tree, AbsKeepWhile1), case KWMet of true -> {ok, Node1, {updated, Path, Result1}}; @@ -739,6 +741,22 @@ insert_or_update_node_cb(_, {interrupted, Reason, Info}, _, _, _) -> Reason1 = ?khepri_error(Reason, Info), {error, Reason1}. +filter_out_irrelevant_keep_while_conds( + Path, {interrupted, node_not_found, #{node_is_target := true}}, KeepWhile) -> + %% A `keep_while' condition on self is irrelevant if the node does not + %% exist yet. + KeepWhile1 = maps:remove(Path, KeepWhile), + %% Likewise for `keep_while' conditions that apply to children of the + %% node. + KeepWhile2 = maps:filter( + fun(KWPath, _) -> + not lists:prefix(Path, KWPath) + end, KeepWhile1), + KeepWhile2; +filter_out_irrelevant_keep_while_conds( + _Path, _Node, KeepWhile) -> + KeepWhile. + gather_node_props_from_old_and_new_nodes(OldNode, NewNode, TreeOptions) -> OldNodeProps = case OldNode of undefined -> diff --git a/test/keep_while_conditions.erl b/test/keep_while_conditions.erl index 15847ca1..ba656680 100644 --- a/test/keep_while_conditions.erl +++ b/test/keep_while_conditions.erl @@ -188,6 +188,29 @@ insert_when_keep_while_false_on_self_test() -> ?assertEqual({ok, #{[foo] => #{}}}, Ret), ?assertEqual([{aux, trigger_delayed_aux_queries_eval}], SE). +insert_when_keep_while_false_on_child_test() -> + S0 = khepri_machine:init(?MACH_PARAMS()), + KeepWhile = #{[?THIS_KHEPRI_NODE, bar] => #if_has_data{}}, + Command = #put{path = [foo], + payload = khepri_payload:data(foo_value), + options = #{keep_while => KeepWhile}}, + {S1, Ret, SE} = khepri_machine:apply(?META, Command, S0), + Root = khepri_machine:get_root(S1), + + ?assertEqual( + #node{ + props = + #{payload_version => 1, + child_list_version => 2}, + child_nodes = + #{foo => + #node{ + props = ?INIT_NODE_PROPS, + payload = khepri_payload:data(foo_value)}}}, + Root), + ?assertEqual({ok, #{[foo] => #{}}}, Ret), + ?assertEqual([{aux, trigger_delayed_aux_queries_eval}], SE). + keep_while_still_true_after_command_test() -> KeepWhile = #{[foo] => #if_child_list_length{count = 0}}, Commands = [#put{path = [foo],