Skip to content

Commit 77e1a78

Browse files
committed
diff BUGFIX user-ordered replace operation
1 parent 2d749a2 commit 77e1a78

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

src/diff.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,6 +2004,86 @@ 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+
* To prevent this, the affected node is unlinked and freed.
2011+
*
2012+
* @param[in,out] diff The node whose metadata has been modified.
2013+
* @param[in] mod The YANG module associated with the metadata.
2014+
* @return LY_ERR value.
2015+
*/
2016+
static LY_ERR
2017+
lyd_diff_find_and_unlink_cyclic_nodes(struct lyd_node **diff, const struct lys_module *mod)
2018+
{
2019+
LY_ERR ret = LY_SUCCESS;
2020+
struct lyd_meta *meta1, *meta2;
2021+
struct lyd_node *diff_iter = *diff;
2022+
char *buff1 = NULL, *buff2 = NULL;
2023+
const char *name = NULL, *name_iter = NULL;
2024+
size_t bufflen1 = 0, buffused1 = 0;
2025+
size_t bufflen2 = 0, buffused2 = 0;
2026+
2027+
/* itereate throught previous nodes and look for logically identical changes */
2028+
while (diff_iter->prev->next) {
2029+
diff_iter = diff_iter->prev;
2030+
2031+
meta1 = lyd_find_meta((*diff)->meta, mod, "key");
2032+
meta2 = lyd_find_meta(diff_iter->meta, mod, "orig-key");
2033+
2034+
name = lyd_get_meta_value(meta1);
2035+
name_iter = lyd_get_meta_value(meta2);
2036+
2037+
if (!name || !name_iter) {
2038+
continue;
2039+
}
2040+
2041+
/* if keys don't match, skip - not a candidate for cyclic change */
2042+
if (strcmp(name, name_iter)) {
2043+
continue;
2044+
}
2045+
2046+
meta1 = lyd_find_meta((*diff)->meta, mod, "orig-key");
2047+
meta2 = lyd_find_meta(diff_iter->meta, mod, "key");
2048+
2049+
/* store string values of metadata to compare later */
2050+
name = lyd_get_meta_value(meta1);
2051+
name_iter = lyd_get_meta_value(meta2);
2052+
2053+
/* reuse buffers by resetting used size */
2054+
buffused1 = buffused2 = 0;
2055+
2056+
if ((ret = lyd_path_list_predicate(*diff, &buff1, &bufflen1, &buffused1, 0))) {
2057+
goto cleanup;
2058+
}
2059+
2060+
if ((ret = lyd_path_list_predicate(diff_iter, &buff2, &bufflen2, &buffused2, 0))) {
2061+
goto cleanup;
2062+
}
2063+
2064+
if (!name || !name_iter) {
2065+
continue;
2066+
}
2067+
2068+
/* compare path predicates with metadata - check if this is a reversed operation */
2069+
if (!strcmp(buff1, name_iter) && !strcmp(buff2, name)) {
2070+
2071+
/* found a cyclic change - remove and free the node */
2072+
if ((ret = lyd_unlink_tree(*diff))) {
2073+
goto cleanup;
2074+
}
2075+
2076+
lyd_free_tree(*diff);
2077+
*diff = NULL;
2078+
goto cleanup;
2079+
}
2080+
}
2081+
cleanup:
2082+
free(buff1);
2083+
free(buff2);
2084+
return ret;
2085+
}
2086+
20072087
/**
20082088
* @brief Update operations on a diff node when the new operation is REPLACE.
20092089
*
@@ -2047,6 +2127,7 @@ lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, con
20472127
meta = lyd_find_meta(src_diff->meta, mod, meta_name);
20482128
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL);
20492129
LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL));
2130+
20502131
break;
20512132
case LYS_LEAF:
20522133
/* replaced with the exact same value, impossible */
@@ -2461,6 +2542,17 @@ lyd_diff_is_redundant(struct lyd_node *diff)
24612542
orig_meta_name = "orig-value";
24622543
}
24632544

2545+
/** userordered lists can have different nodes that lead to identical changes.
2546+
* Only one node will stay other is unlinked
2547+
*/
2548+
if (!strcmp(meta_name, "key")) {
2549+
LY_CHECK_RET(lyd_diff_find_and_unlink_cyclic_nodes(&diff, mod), 0);
2550+
if (!diff) {
2551+
return 0;
2552+
}
2553+
2554+
}
2555+
24642556
/* check for redundant move */
24652557
orig_val_meta = lyd_find_meta(diff->meta, mod, orig_meta_name);
24662558
val_meta = lyd_find_meta(diff->meta, mod, meta_name);

tests/utests/data/test_diff.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ const char *schema =
9090
" leaf l3 {type string;}"
9191
" }"
9292
" }"
93+
" list person {key \"name surname\"; ordered-by user;"
94+
" leaf name {type string;}"
95+
" leaf surname {type string;}"
96+
" }"
9397
" leaf-list dllist {type uint8; default \"1\"; default \"2\"; default \"3\";}"
9498
" list list {key \"name\";"
9599
" leaf name {type string;}"
@@ -1418,6 +1422,63 @@ test_metadata(void **state)
14181422
TEST_DIFF_3(xml1, xml2, xml3, LYD_DIFF_META, out_diff_1, out_diff_2, out_merge);
14191423
}
14201424

1425+
static void
1426+
test_userord_conflicting_replace(void **state)
1427+
{
1428+
(void) state;
1429+
struct lyd_node *diff_tree1 = NULL, *diff_tree2 = NULL, *final_diff = NULL;
1430+
char *final_xml;
1431+
1432+
char *diff_xml1 =
1433+
"<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
1434+
" <person yang:operation=\"delete\" yang:orig-key=\"[name='alice'][surname='chalice']\">"
1435+
" <name>roland</name>"
1436+
" <surname>doland</surname>"
1437+
" </person>"
1438+
" <person yang:operation=\"replace\" yang:orig-key=\"\" yang:key=\"[name='bob'][surname='drop']\">"
1439+
" <name>jake</name>"
1440+
" <surname>fake</surname>"
1441+
" </person>"
1442+
" <person yang:operation=\"replace\" yang:orig-key=\"[name='jake'][surname='fake']\" yang:key=\"[name='alice'][surname='chalice']\">"
1443+
" <name>bob</name>"
1444+
" <surname>drop</surname>"
1445+
" </person>"
1446+
"</df>\n";
1447+
1448+
char *diff_xml2 =
1449+
"<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
1450+
" <person yang:operation=\"replace\" yang:orig-key=\"[name='alice'][surname='chalice']\" yang:key=\"\">"
1451+
" <name>bob</name>"
1452+
" <surname>drop</surname>"
1453+
" </person>"
1454+
"</df>\n";
1455+
1456+
CHECK_PARSE_LYD(diff_xml1, diff_tree1);
1457+
CHECK_PARSE_LYD(diff_xml2, diff_tree2);
1458+
lyd_diff_merge_all(&final_diff, diff_tree1, 0);
1459+
lyd_diff_merge_all(&final_diff, diff_tree2, 0);
1460+
1461+
lyd_print_mem(&final_xml, final_diff, LYD_XML, LYD_PRINT_WITHSIBLINGS);
1462+
1463+
char *result_xml =
1464+
"<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
1465+
" <person yang:operation=\"delete\" yang:orig-key=\"[name='alice'][surname='chalice']\">\n"
1466+
" <name>roland</name>\n"
1467+
" <surname>doland</surname>\n"
1468+
" </person>\n"
1469+
" <person yang:operation=\"replace\" yang:orig-key=\"\" yang:key=\"[name='bob'][surname='drop']\">\n"
1470+
" <name>jake</name>\n"
1471+
" <surname>fake</surname>\n"
1472+
" </person>\n"
1473+
"</df>\n";
1474+
1475+
assert_string_equal(final_xml, result_xml);
1476+
free(final_xml);
1477+
lyd_free_all(diff_tree1);
1478+
lyd_free_all(diff_tree2);
1479+
lyd_free_all(final_diff);
1480+
}
1481+
14211482
int
14221483
main(void)
14231484
{
@@ -1442,6 +1503,7 @@ main(void)
14421503
UTEST(test_state_llist, setup),
14431504
UTEST(test_wd, setup),
14441505
UTEST(test_metadata, setup),
1506+
UTEST(test_userord_conflicting_replace, setup),
14451507
};
14461508

14471509
return cmocka_run_group_tests(tests, NULL, NULL);

0 commit comments

Comments
 (0)