Skip to content

Commit af3f076

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

File tree

2 files changed

+153
-3
lines changed

2 files changed

+153
-3
lines changed

src/diff.c

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2004,18 +2004,92 @@ 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.
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 *metas[2];
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 != NULL) {
2029+
diff_iter = diff_iter->prev;
2030+
metas[0] = lyd_find_meta((*diff)->meta, mod, "key");
2031+
metas[1] = lyd_find_meta(diff_iter->meta, mod, "orig-key");
2032+
2033+
/* If keys don't match, skip - not a candidate for cyclic change */
2034+
if (strcmp(lyd_get_meta_value(metas[0]), lyd_get_meta_value(metas[1]))) {
2035+
continue;
2036+
}
2037+
2038+
metas[0] = lyd_find_meta((*diff)->meta, mod, "orig-key");
2039+
metas[1] = lyd_find_meta(diff_iter->meta, mod, "key");
2040+
2041+
/* Store string values of metadata to compare later */
2042+
name = lyd_get_meta_value(metas[0]);
2043+
name_iter = lyd_get_meta_value(metas[1]);
2044+
2045+
if ((ret = lyd_path_list_predicate(*diff, &buff1, &bufflen1, &buffused1, 0))) {
2046+
return ret;
2047+
}
2048+
2049+
if ((ret = lyd_path_list_predicate(diff_iter, &buff2, &bufflen2, &buffused2, 0))) {
2050+
return ret;
2051+
}
2052+
2053+
/* Compare path predicates with metadata - check if this is a reversed operation */
2054+
if (!strcmp(buff1, name_iter) && !strcmp(buff2, name)) {
2055+
2056+
/* Found a cyclic change - remove and free the node */
2057+
if ((ret = lyd_unlink_tree(*diff))) {
2058+
return ret;
2059+
}
2060+
2061+
lyd_free_all(*diff);
2062+
free(buff1);
2063+
free(buff2);
2064+
*diff = NULL;
2065+
return ret;
2066+
}
2067+
2068+
free(buff1);
2069+
buff1 = NULL;
2070+
bufflen1 = 0;
2071+
buffused1 = 0;
2072+
free(buff2);
2073+
buff2 = NULL;
2074+
bufflen2 = 0;
2075+
buffused2 = 0;
2076+
}
2077+
return ret;
2078+
}
2079+
20072080
/**
20082081
* @brief Update operations on a diff node when the new operation is REPLACE.
20092082
*
2010-
* @param[in] diff_match Node from the diff.
2083+
* @param[in,out] diff_node Node from the diff.
20112084
* @param[in] cur_op Current operation of @p diff_match.
20122085
* @param[in] src_diff Current source diff node.
20132086
* @return LY_ERR value.
20142087
*/
20152088
static LY_ERR
2016-
lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff)
2089+
lyd_diff_merge_replace(struct lyd_node **diff_node, enum lyd_diff_op cur_op, const struct lyd_node *src_diff)
20172090
{
20182091
LY_ERR ret;
2092+
struct lyd_node *diff_match = *diff_node;
20192093
const char *str_val, *meta_name, *orig_meta_name;
20202094
struct lyd_meta *meta;
20212095
const struct lys_module *mod;
@@ -2047,6 +2121,14 @@ lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, con
20472121
meta = lyd_find_meta(src_diff->meta, mod, meta_name);
20482122
LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL);
20492123
LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL));
2124+
2125+
/** userordered lists can have different nodes that lead to identical changes.
2126+
* Only one node will stay other is unlinked
2127+
*/
2128+
if (!strcmp(meta_name, "key")) {
2129+
LY_CHECK_RET(lyd_diff_find_and_unlink_cyclic_nodes(diff_node, mod));
2130+
}
2131+
20502132
break;
20512133
case LYS_LEAF:
20522134
/* replaced with the exact same value, impossible */
@@ -2720,7 +2802,13 @@ lyd_diff_merge_r(const struct lyd_node *src_diff, struct lyd_node *diff_parent,
27202802
/* merge operations */
27212803
switch (src_op) {
27222804
case LYD_DIFF_OP_REPLACE:
2723-
ret = lyd_diff_merge_replace(diff_node, cur_op, src_diff);
2805+
ret = lyd_diff_merge_replace(&diff_node, cur_op, src_diff);
2806+
2807+
/* diff_node was unlinked and do not need any further changes */
2808+
if (!diff_node) {
2809+
return LY_SUCCESS;
2810+
}
2811+
27242812
break;
27252813
case LYD_DIFF_OP_CREATE:
27262814
if ((cur_op == LYD_DIFF_OP_CREATE) && lysc_is_dup_inst_list(diff_node->schema)) {

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)