@@ -2004,6 +2004,197 @@ 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+
2104+ LY_CHECK_GOTO (ret = lyd_path_list_predicate (diff_iter , & buff2 , & bufflen2 , & buffused2 , 0 ), cleanup );
2105+
2106+ /* compare path predicates with metadata - check if this is a reversed operation */
2107+ if (!strcmp (buff1 , name_iter ) && !strcmp (buff2 , name )) {
2108+
2109+ /* found a cyclic change - remove and free the node */
2110+ ret = 1 ;
2111+ goto cleanup ;
2112+ }
2113+ } else {
2114+ llist_value1 = lyd_get_value (* diff );
2115+
2116+ llist_value2 = lyd_get_value (diff_iter );
2117+
2118+ /* compare vlaue of data node with metadata - check if this is a reversed operation */
2119+ if (!strcmp (llist_value1 , name_iter ) && !strcmp (llist_value2 , name )) {
2120+
2121+ /* found a cyclic change - remove and free the node */
2122+ ret = 1 ;
2123+ goto cleanup ;
2124+ }
2125+ }
2126+ next_iter :
2127+ diff_iter = diff_iter -> prev ;
2128+ }
2129+
2130+ cleanup :
2131+ free (buff1 );
2132+ free (buff2 );
2133+ return ret ;
2134+ }
2135+
2136+ /**
2137+ * @brief Propagate key/value metadata from a list or leaf-list node
2138+ * to its sibling nodes that reference it via key/value.
2139+ *
2140+ * This is used to ensure correct ordering in user-ordered lists
2141+ * or leaf-lists by updating the corresponding metadata in sibling
2142+ * nodes before the reference node changes.
2143+ *
2144+ * @param[in] diff Node from lyd_diff_merge_replace().
2145+ * @param[in] meta_name Name of the metadata ("key" or "value").
2146+ * @return LY_ERR value.
2147+ */
2148+ static LY_ERR
2149+ lyd_diff_propagate_meta (struct lyd_node * diff , const char * meta_name )
2150+ {
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 (lyd_dup_meta_single (meta2 , diff_iter , NULL ), cleanup );
2175+ }
2176+ } else {
2177+
2178+ buffused = 0 ;
2179+
2180+ LY_CHECK_GOTO (lyd_path_list_predicate (diff , & buff , & bufflen , & buffused , 0 ), cleanup );
2181+ meta_value = lyd_get_meta_value (meta1 );
2182+
2183+ /* if the path predicate matches, replace the metadata */
2184+ if (!strcmp (buff , meta_value )) {
2185+ lyd_diff_del_meta (diff_iter , meta_name );
2186+ meta2 = lyd_find_meta (diff -> meta , mod , meta_name );
2187+ LY_CHECK_GOTO (lyd_dup_meta_single (meta2 , diff_iter , NULL ), cleanup );
2188+ }
2189+ }
2190+ }
2191+ }
2192+
2193+ cleanup :
2194+ free (buff );
2195+ return LY_SUCCESS ;
2196+ }
2197+
20072198/**
20082199 * @brief Update operations on a diff node when the new operation is REPLACE.
20092200 *
@@ -2043,10 +2234,14 @@ lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, con
20432234 meta_name = "value" ;
20442235 }
20452236
2237+ /* update sibling nodes which reference the diff_match by key/value */
2238+ LY_CHECK_RET (lyd_diff_propagate_meta (diff_match , meta_name ));
2239+
20462240 lyd_diff_del_meta (diff_match , meta_name );
20472241 meta = lyd_find_meta (src_diff -> meta , mod , meta_name );
20482242 LY_CHECK_ERR_RET (!meta , LOGERR_META (ctx , meta_name , src_diff ), LY_EINVAL );
20492243 LY_CHECK_RET (lyd_dup_meta_single (meta , diff_match , NULL ));
2244+
20502245 break ;
20512246 case LYS_LEAF :
20522247 /* replaced with the exact same value, impossible */
@@ -2429,10 +2624,10 @@ static ly_bool
24292624lyd_diff_is_redundant (struct lyd_node * diff )
24302625{
24312626 enum lyd_diff_op op ;
2432- struct lyd_meta * meta , * orig_val_meta = NULL , * val_meta = NULL ;
2627+ struct lyd_meta * meta ;
24332628 struct lyd_node * child ;
24342629 const struct lys_module * mod ;
2435- const char * str , * orig_meta_name , * meta_name ;
2630+ const char * str ;
24362631
24372632 assert (diff );
24382633
@@ -2449,42 +2644,12 @@ lyd_diff_is_redundant(struct lyd_node *diff)
24492644 LY_CHECK_RET (lyd_diff_get_op (diff , & op , NULL ), 0 );
24502645
24512646 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- }
24782647
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- }
2648+ /** userordered lists can have different nodes that lead to identical changes.
2649+ * if such a redundant node is detected, this function returns non-zero.
2650+ */
2651+ LY_CHECK_RET (lyd_diff_is_redundant_userord_move (& diff , child ), 1 );
2652+
24882653 } else if (op == LYD_DIFF_OP_NONE ) {
24892654 if (!diff -> schema ) {
24902655 /* opaque node with none must be redundant */
@@ -3108,6 +3273,7 @@ lyd_diff_reverse_userord(struct lyd_node *node, const struct lysc_node **schema_
31083273 do {
31093274 -- u ;
31103275 LY_CHECK_GOTO (rc = lyd_insert_after (anchor , (* nodes_p )[u ]), cleanup );
3276+ anchor = (* nodes_p )[u ];
31113277 } while (u );
31123278 }
31133279
@@ -3268,13 +3434,13 @@ lyd_diff_reverse_siblings_r(struct lyd_node *sibling, const struct lys_module *y
32683434 LY_CHECK_GOTO (rc = lyd_diff_reverse_siblings_r (lyd_child (iter ), yang_mod ), cleanup );
32693435
32703436 if (lysc_is_userordered (iter -> schema )) {
3271- /* special user-ordered nodes processing */
3437+ /* special user-ordered nodes processing (collect all the nodes) */
32723438 LY_CHECK_GOTO (rc = lyd_diff_reverse_userord (iter , & userord_schema , & userord ), cleanup );
32733439 }
32743440 }
32753441
32763442 if (userord_schema ) {
3277- /* finish user-ordered nodes processing */
3443+ /* finish user-ordered nodes processing - reverse the order of the nodes */
32783444 LY_CHECK_GOTO (rc = lyd_diff_reverse_userord (NULL , & userord_schema , & userord ), cleanup );
32793445 }
32803446
0 commit comments