Skip to content

Commit 4d1b567

Browse files
Ardivalmichalvasko
authored andcommitted
diff BUGFIX user-ordered replace operation
1 parent 4a084d3 commit 4d1b567

File tree

2 files changed

+486
-39
lines changed

2 files changed

+486
-39
lines changed

src/diff.c

Lines changed: 204 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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
24292623
lyd_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

Comments
 (0)