@@ -2004,6 +2004,196 @@ lyd_diff_change_op(struct lyd_node *node, enum lyd_diff_op op)
20042004 }
20052005}
20062006
2007+ /**
2008+ * @brief In user-ordered lists, certain operations on sibling nodes can result in logically identical changes.
2009+ * However, applying the first change may cause the second one to fail.
2010+ * Check whether this diff node is redundant.
2011+ *
2012+ * @param[in,out] diff The node whose metadata has been modified.
2013+ * @param[in] child The child of diff node.
2014+ * @return 0 if not, non-zero if it is.
2015+ */
2016+ static ly_bool
2017+ lyd_diff_is_redundant_userord_move (struct lyd_node * * diff , struct lyd_node * child )
2018+ {
2019+ LY_ERR ret = LY_SUCCESS ;
2020+ struct lyd_meta * meta1 , * meta2 ;
2021+ struct lyd_meta * orig_val_meta = NULL , * val_meta = NULL ;
2022+ struct lyd_node * diff_iter = * diff ;
2023+ char * buff1 = NULL , * buff2 = NULL ;
2024+ const char * llist_value1 = NULL , * llist_value2 = NULL ;
2025+ const char * name = NULL , * name_iter = NULL ;
2026+ size_t bufflen1 = 0 , buffused1 = 0 ;
2027+ size_t bufflen2 = 0 , buffused2 = 0 ;
2028+ const char * orig_meta_name , * meta_name ;
2029+
2030+ /* get metadata names */
2031+ if (lysc_is_dup_inst_list ((* diff )-> schema )) {
2032+ meta_name = "yang:position" ;
2033+ orig_meta_name = "yang:orig-position" ;
2034+ } else if ((* diff )-> schema -> nodetype == LYS_LIST ) {
2035+ meta_name = "yang:key" ;
2036+ orig_meta_name = "yang:orig-key" ;
2037+ } else {
2038+ meta_name = "yang:value" ;
2039+ orig_meta_name = "yang:orig-value" ;
2040+ }
2041+
2042+ /* check for redundant move */
2043+ orig_val_meta = lyd_find_meta ((* diff )-> meta , NULL , orig_meta_name );
2044+ val_meta = lyd_find_meta ((* diff )-> meta , NULL , meta_name );
2045+ assert (orig_val_meta && val_meta );
2046+
2047+ if (!lyd_compare_meta (orig_val_meta , val_meta )) {
2048+ /* there is actually no move */
2049+ lyd_free_meta_single (orig_val_meta );
2050+ lyd_free_meta_single (val_meta );
2051+ if (child ) {
2052+ /* change operation to NONE, we have siblings */
2053+ lyd_diff_change_op ((* diff ), LYD_DIFF_OP_NONE );
2054+ goto cleanup ;
2055+ }
2056+
2057+ /* redundant node, BUT !!
2058+ * In diff the move operation is always converted to be INSERT_AFTER, which is fine
2059+ * because the data that this is applied on should not change for the diff lifetime.
2060+ * However, when we are merging 2 diffs, this conversion is actually lossy because
2061+ * if the data change, the move operation can also change its meaning. In this specific
2062+ * case the move operation will be lost. But it can be considered a feature, it is not supported.
2063+ */
2064+ ret = 1 ;
2065+ goto cleanup ;
2066+ }
2067+
2068+ /* itereate throught previous nodes and look for logically identical changes */
2069+ diff_iter = (* diff )-> prev ;
2070+ while (diff_iter != (* diff )) {
2071+
2072+ meta1 = lyd_find_meta ((* diff )-> meta , NULL , meta_name );
2073+ meta2 = lyd_find_meta (diff_iter -> meta , NULL , orig_meta_name );
2074+
2075+ name = lyd_get_meta_value (meta1 );
2076+ name_iter = lyd_get_meta_value (meta2 );
2077+
2078+ if (!name || !name_iter ) {
2079+ goto next_iter ;
2080+ }
2081+
2082+ /* if keys don't match, skip - not a candidate for cyclic change */
2083+ if (strcmp (name , name_iter )) {
2084+ goto next_iter ;
2085+ }
2086+
2087+ meta1 = lyd_find_meta ((* diff )-> meta , NULL , orig_meta_name );
2088+ meta2 = lyd_find_meta (diff_iter -> meta , NULL , meta_name );
2089+
2090+ /* store string values of metadata to compare later */
2091+ name = lyd_get_meta_value (meta1 );
2092+ name_iter = lyd_get_meta_value (meta2 );
2093+
2094+ if (!name || !name_iter ) {
2095+ goto next_iter ;
2096+ }
2097+
2098+ if ((* diff )-> schema -> nodetype == LYS_LIST ) {
2099+
2100+ /* reuse buffers by resetting used size */
2101+ buffused1 = buffused2 = 0 ;
2102+ LY_CHECK_GOTO (ret = lyd_path_list_predicate (* diff , & buff1 , & bufflen1 , & buffused1 , 0 ), cleanup );
2103+ LY_CHECK_GOTO (ret = lyd_path_list_predicate (diff_iter , & buff2 , & bufflen2 , & buffused2 , 0 ), cleanup );
2104+
2105+ /* compare path predicates with metadata - check if this is a reversed operation */
2106+ if (!strcmp (buff1 , name_iter ) && !strcmp (buff2 , name )) {
2107+
2108+ /* found a cyclic change - remove and free the node */
2109+ ret = 1 ;
2110+ goto cleanup ;
2111+ }
2112+ } else {
2113+ llist_value1 = lyd_get_value (* diff );
2114+ llist_value2 = lyd_get_value (diff_iter );
2115+
2116+ /* compare vlaue of data node with metadata - check if this is a reversed operation */
2117+ if (!strcmp (llist_value1 , name_iter ) && !strcmp (llist_value2 , name )) {
2118+
2119+ /* found a cyclic change - remove and free the node */
2120+ ret = 1 ;
2121+ goto cleanup ;
2122+ }
2123+ }
2124+
2125+ next_iter :
2126+ diff_iter = diff_iter -> prev ;
2127+ }
2128+
2129+ cleanup :
2130+ free (buff1 );
2131+ free (buff2 );
2132+ return ret ;
2133+ }
2134+
2135+ /**
2136+ * @brief Propagate key/value metadata from a list or leaf-list node
2137+ * to its sibling nodes that reference it via key/value.
2138+ *
2139+ * This is used to ensure correct ordering in user-ordered lists
2140+ * or leaf-lists by updating the corresponding metadata in sibling
2141+ * nodes before the reference node changes.
2142+ *
2143+ * @param[in] diff Node from lyd_diff_merge_replace().
2144+ * @param[in] meta_name Name of the metadata ("key" or "value").
2145+ * @return LY_ERR value.
2146+ */
2147+ static LY_ERR
2148+ lyd_diff_propagate_meta (struct lyd_node * diff , const char * meta_name )
2149+ {
2150+ LY_ERR ret = LY_SUCCESS ;
2151+ const struct lys_module * mod ;
2152+ struct lyd_meta * meta1 , * meta2 ;
2153+ struct lyd_node * diff_iter = NULL ;
2154+ char * buff = NULL ;
2155+ size_t bufflen = 0 , buffused = 0 ;
2156+ const char * meta_value ;
2157+
2158+ /* get "yang" module for the metadata */
2159+ mod = ly_ctx_get_module_latest (LYD_CTX (diff ), "yang" );
2160+ assert (mod );
2161+
2162+ /*
2163+ * iterate through all siblings of the diff
2164+ * if a sibling references the diff node via metadata, update it
2165+ */
2166+ LY_LIST_FOR (diff , diff_iter ) {
2167+ /* find the relevant metadata on the current sibling */
2168+ if ((meta1 = lyd_find_meta (diff_iter -> meta , mod , meta_name ))) {
2169+ if (diff -> schema -> nodetype == LYS_LEAFLIST ) {
2170+ if (!strcmp (lyd_get_meta_value (meta1 ), lyd_get_value (diff ))) {
2171+ /* replace the old metadata with the updated one from the changed node */
2172+ lyd_diff_del_meta (diff_iter , meta_name );
2173+ meta2 = lyd_find_meta (diff -> meta , mod , meta_name );
2174+ LY_CHECK_GOTO ((ret = lyd_dup_meta_single (meta2 , diff_iter , NULL )), cleanup );
2175+ }
2176+ } else {
2177+ buffused = 0 ;
2178+
2179+ LY_CHECK_GOTO ((ret = lyd_path_list_predicate (diff , & buff , & bufflen , & buffused , 0 )), cleanup );
2180+ meta_value = lyd_get_meta_value (meta1 );
2181+
2182+ /* if the path predicate matches, replace the metadata */
2183+ if (!strcmp (buff , meta_value )) {
2184+ meta1 = lyd_find_meta (diff_iter -> meta , mod , meta_name );
2185+ meta2 = lyd_find_meta (diff -> meta , mod , meta_name );
2186+ LY_CHECK_GOTO ((ret = lyd_change_meta (meta1 , lyd_get_meta_value (meta2 ))), cleanup );
2187+ }
2188+ }
2189+ }
2190+ }
2191+
2192+ cleanup :
2193+ free (buff );
2194+ return ret ;
2195+ }
2196+
20072197/**
20082198 * @brief Update operations on a diff node when the new operation is REPLACE.
20092199 *
@@ -2043,10 +2233,14 @@ lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, con
20432233 meta_name = "value" ;
20442234 }
20452235
2236+ /* update sibling nodes which reference the diff_match by key/value */
2237+ LY_CHECK_RET (lyd_diff_propagate_meta (diff_match , meta_name ));
2238+
20462239 lyd_diff_del_meta (diff_match , meta_name );
20472240 meta = lyd_find_meta (src_diff -> meta , mod , meta_name );
20482241 LY_CHECK_ERR_RET (!meta , LOGERR_META (ctx , meta_name , src_diff ), LY_EINVAL );
20492242 LY_CHECK_RET (lyd_dup_meta_single (meta , diff_match , NULL ));
2243+
20502244 break ;
20512245 case LYS_LEAF :
20522246 /* replaced with the exact same value, impossible */
@@ -2429,10 +2623,10 @@ static ly_bool
24292623lyd_diff_is_redundant (struct lyd_node * diff )
24302624{
24312625 enum lyd_diff_op op ;
2432- struct lyd_meta * meta , * orig_val_meta = NULL , * val_meta = NULL ;
2626+ struct lyd_meta * meta ;
24332627 struct lyd_node * child ;
24342628 const struct lys_module * mod ;
2435- const char * str , * orig_meta_name , * meta_name ;
2629+ const char * str ;
24362630
24372631 assert (diff );
24382632
@@ -2449,42 +2643,12 @@ lyd_diff_is_redundant(struct lyd_node *diff)
24492643 LY_CHECK_RET (lyd_diff_get_op (diff , & op , NULL ), 0 );
24502644
24512645 if ((op == LYD_DIFF_OP_REPLACE ) && lysc_is_userordered (diff -> schema )) {
2452- /* get metadata names */
2453- if (lysc_is_dup_inst_list (diff -> schema )) {
2454- meta_name = "position" ;
2455- orig_meta_name = "orig-position" ;
2456- } else if (diff -> schema -> nodetype == LYS_LIST ) {
2457- meta_name = "key" ;
2458- orig_meta_name = "orig-key" ;
2459- } else {
2460- meta_name = "value" ;
2461- orig_meta_name = "orig-value" ;
2462- }
2463-
2464- /* check for redundant move */
2465- orig_val_meta = lyd_find_meta (diff -> meta , mod , orig_meta_name );
2466- val_meta = lyd_find_meta (diff -> meta , mod , meta_name );
2467- assert (orig_val_meta && val_meta );
2468-
2469- if (!lyd_compare_meta (orig_val_meta , val_meta )) {
2470- /* there is actually no move */
2471- lyd_free_meta_single (orig_val_meta );
2472- lyd_free_meta_single (val_meta );
2473- if (child ) {
2474- /* change operation to NONE, we have siblings */
2475- lyd_diff_change_op (diff , LYD_DIFF_OP_NONE );
2476- return 0 ;
2477- }
24782646
2479- /* redundant node, BUT !!
2480- * In diff the move operation is always converted to be INSERT_AFTER, which is fine
2481- * because the data that this is applied on should not change for the diff lifetime.
2482- * However, when we are merging 2 diffs, this conversion is actually lossy because
2483- * if the data change, the move operation can also change its meaning. In this specific
2484- * case the move operation will be lost. But it can be considered a feature, it is not supported.
2485- */
2486- return 1 ;
2487- }
2647+ /** userordered lists can have different nodes that lead to identical changes.
2648+ * if such a redundant node is detected, this function returns non-zero.
2649+ */
2650+ LY_CHECK_RET (lyd_diff_is_redundant_userord_move (& diff , child ), 1 );
2651+
24882652 } else if (op == LYD_DIFF_OP_NONE ) {
24892653 if (!diff -> schema ) {
24902654 /* opaque node with none must be redundant */
@@ -3108,6 +3272,7 @@ lyd_diff_reverse_userord(struct lyd_node *node, const struct lysc_node **schema_
31083272 do {
31093273 -- u ;
31103274 LY_CHECK_GOTO (rc = lyd_insert_after (anchor , (* nodes_p )[u ]), cleanup );
3275+ anchor = (* nodes_p )[u ];
31113276 } while (u );
31123277 }
31133278
@@ -3268,13 +3433,13 @@ lyd_diff_reverse_siblings_r(struct lyd_node *sibling, const struct lys_module *y
32683433 LY_CHECK_GOTO (rc = lyd_diff_reverse_siblings_r (lyd_child (iter ), yang_mod ), cleanup );
32693434
32703435 if (lysc_is_userordered (iter -> schema )) {
3271- /* special user-ordered nodes processing */
3436+ /* special user-ordered nodes processing (collect all the nodes) */
32723437 LY_CHECK_GOTO (rc = lyd_diff_reverse_userord (iter , & userord_schema , & userord ), cleanup );
32733438 }
32743439 }
32753440
32763441 if (userord_schema ) {
3277- /* finish user-ordered nodes processing */
3442+ /* finish user-ordered nodes processing - reverse the order of the nodes */
32783443 LY_CHECK_GOTO (rc = lyd_diff_reverse_userord (NULL , & userord_schema , & userord ), cleanup );
32793444 }
32803445
0 commit comments