Skip to content

Commit b7314d9

Browse files
committed
diff BUGFIX user-ordered replace operation
1 parent 2f1ecb9 commit b7314d9

File tree

2 files changed

+487
-39
lines changed

2 files changed

+487
-39
lines changed

src/diff.c

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

Comments
 (0)