From: Alan T. DeKok Date: Wed, 15 Dec 2021 14:39:01 +0000 (-0500) Subject: add support for intersection X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=950aaa768a865675a107a7ace579d515f8926eef;p=thirdparty%2Ffreeradius-server.git add support for intersection --- diff --git a/src/lib/util/edit.c b/src/lib/util/edit.c index e792282f8f..3581735ccb 100644 --- a/src/lib/util/edit.c +++ b/src/lib/util/edit.c @@ -987,6 +987,117 @@ static int list_merge_rhs(fr_edit_list_t *el, fr_pair_t *dst, fr_pair_list_t *sr return 0; } +/** A INTERSECTION B + * + */ +static int list_intersection(fr_edit_list_t *el, fr_pair_t *dst, fr_pair_list_t *src) +{ + fr_pair_t *a, *b; + fr_dcursor_t cursor1, cursor2; + + /* + * Prevent people from doing stupid things. + */ + if (dst->vp_type == FR_TYPE_STRUCT) { + fr_strerror_printf("Cannot take intersection of STRUCT data types, it would break the structure"); + return -1; + } + + fr_pair_list_sort(&dst->children, fr_pair_cmp_by_parent_num); + fr_pair_list_sort(src, fr_pair_cmp_by_parent_num); + + fr_pair_dcursor_init(&cursor1, &dst->children); + fr_pair_dcursor_init(&cursor2, src); + + while (true) { + int rcode; + + a = fr_dcursor_current(&cursor1); + b = fr_dcursor_current(&cursor2); + + /* + * A is done, so we can return. + */ + if (!a) break; + + /* + * B is done, so we delete everything else in A. + */ + if (!b) { + delete: + fr_dcursor_next(&cursor1); + if (fr_edit_list_pair_delete(el, &dst->children, a) < 0) return -1; + continue; + } + + rcode = fr_pair_cmp_by_parent_num(a, b); + + /* + * a < b + * + * A gets removed. + */ + if (rcode < 0) goto delete; + + /* + * a > b + * + * Skip forward in B until we have it better matching A. + */ + if (rcode > 0) { + fr_dcursor_next(&cursor2); + continue; + } + + fr_assert(rcode == 0); + + /* + * INTERSECT the children, and then leave A + * alone, unless it's empty, in which case A + * INTERSECT B is empty, so we also delete A. + */ + if (fr_type_is_structural(a->vp_type)) { + rcode = list_intersection(el, a, &b->children); + if (rcode < 0) return rcode; + + if (fr_pair_list_empty(&a->children)) { + if (fr_edit_list_pair_delete(el, &dst->children, a) < 0) return -1; + } + + fr_dcursor_next(&cursor1); + fr_dcursor_next(&cursor2); + continue; + } + + /* + * Process all identical attributes, but by + * value. + */ + while (a && b && (a->da == b->da)) { + fr_pair_t *next1, *next2; + + next1 = fr_dcursor_next(&cursor1); + next2 = fr_dcursor_next(&cursor2); + + /* + * Check if the values are the same. This + * returns 0 for "equal", and non-zero for + * anything else. + */ + rcode = fr_value_box_cmp(&a->data, &b->data); + if (rcode != 0) { + if (fr_edit_list_pair_delete(el, &dst->children, a) < 0) return -1; + } + + a = next1; + b = next2; + } + } + + return 0; +} + + /** Apply operators to lists. * * = is "if found vp, do nothing. Otherwise call fr_edit_list_insert_pair_tail() @@ -1038,6 +1149,11 @@ int fr_edit_list_apply_list_assignment(fr_edit_list_t *el, fr_pair_t *dst, fr_to COPY; return fr_edit_list_insert_list_head(el, &dst->children, ©); + case T_OP_AND_EQ: + if (&dst->children == src) return 0; /* A INTERSECTION A == A */ + + return list_intersection(el, dst, src); + case T_OP_OR_EQ: if (&dst->children == src) return 0; /* A UNION A == A */ diff --git a/src/tests/keywords/edit-intersection b/src/tests/keywords/edit-intersection new file mode 100644 index 0000000000..1edf787297 --- /dev/null +++ b/src/tests/keywords/edit-intersection @@ -0,0 +1,45 @@ +# +# PRE: edit-list +# + +update control { + &Tmp-String-0 := "foo" +} + +update reply { + &Tmp-String-0 := "foo" +} + +&reply &= &control + +# must exist +if (!&reply.Tmp-String-0) { + %(debug_attr:reply[*]) + test_fail +} + +# and have the correct value +if (&reply.Tmp-String-0 != "foo") { + %(debug_attr:reply[*]) + test_fail +} + +# reset +&reply -= &Tmp-String-0[*] + +# +# Same attribute, but different value +# +update reply { + &Tmp-String-0 := "bar" +} + +&reply &= &control + +# must NOT exist +if (&reply.Tmp-String-0) { + %(debug_attr:reply[*]) + test_fail +} + +success