From: Alan T. DeKok Date: Fri, 29 Jul 2022 18:18:33 +0000 (-0400) Subject: allow removing from list by attribute and value. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5b87698ae18a60d26b664afa9756be384e202fba;p=thirdparty%2Ffreeradius-server.git allow removing from list by attribute and value. With documentation and tests. --- diff --git a/doc/antora/modules/reference/pages/unlang/edit.adoc b/doc/antora/modules/reference/pages/unlang/edit.adoc index d385e434800..0861690796d 100644 --- a/doc/antora/modules/reference/pages/unlang/edit.adoc +++ b/doc/antora/modules/reference/pages/unlang/edit.adoc @@ -73,10 +73,10 @@ In short, failed edit operations are effectively a "noop" operation, and do not result in any changes. Multiple attributes may be grouped into a set by using the `group` -keyword. This keyword is not usually needed, and offers no benefit -other than grouping changes to multiple attributes. When changes are -done in a `group`, then either all of the changes are applied, or none -of them are applied. +keyword. When changes are done in a `group`, then either all of the +changes are applied, or none of them are applied. This functionality +is best used to conditionally apply attribute changes, generally when +retrieving data from a database. .Grouping multiple edits [source,unlang] @@ -117,9 +117,9 @@ the list operations are simple, and well defined. | := | Override the list to the contents with the __. If the list already exists, its value is over-written. If the list does not exist, it is created, and the contents set to thex value of the __ | += | Perform list append operation. The contents of the __ are appended to the __. The resulting list is __. | ^= | Perform a list prepend operation. The contents of the __ are prepended to the __. The resulting list is __. -| -= | Remove attributes from the __ which match the __ +| -= | Remove attributes from the __ which match the __ attribute or list. | \|= | Perform a list union. The resulting list has all of the contents of the original __, and the __ list. -| \&= | Perform a list intersection. The resulting list has only the attributes which are in both the origianl __, and the __ list. +| &= | Perform a list intersection. The resulting list has only the attributes which are in both the origianl __, and the __ list. | >= | Perform a priority merge of two lists. The resulting list has all of the contents of the original __, and of all __ list attributes which are not already in __. | \<= | Perform a priority merge of two lists. The resulting list has all of the contents of the original __, and of all __ list attributes which are not in __ are left alone.. |===== @@ -251,6 +251,17 @@ Attributes can be removed from a list using the `-=` (remove) operator. ---- ==== +.Remove instance of `Filter-Id` which have value `bar` +==== +[source,unlang] +---- +&reply -= { + &Filter-Id = "bar" +} +---- +==== + + This syntax is clearer and more consistent than the old `!* ANY` hacks. @@ -341,7 +352,7 @@ change the attribute _value_. | *= | Perform multiplication. The value of the __ is multiplied by the contents of the __. | /= | Perform subtraction. The value of the __ is divided by the contents of the __. | \|= | Perform logical "or". The value of the __ is "or"ed with the contents of the __. -| \&= | Perform logical "and". The value of the __ is "and"ed with the contents of the __. +| &= | Perform logical "and". The value of the __ is "and"ed with the contents of the __. | \<<= | Perform left shift. The value of the __ is shifted left by the value of __ | \>>= | Perform right shift. The value of the __ is shifted right by the value of __ |===== diff --git a/src/lib/util/edit.c b/src/lib/util/edit.c index 9d1a4bbb8b2..9d9788ffdbe 100644 --- a/src/lib/util/edit.c +++ b/src/lib/util/edit.c @@ -794,6 +794,40 @@ int fr_edit_list_insert_list_after(fr_edit_list_t *el, fr_pair_list_t *list, fr_ return 0; } +/** Removes elements matching a list + * + * O(N^2) unfortunately. + */ +static int fr_edit_list_delete_list(fr_edit_list_t *el, fr_pair_list_t *list, fr_pair_list_t *to_remove) +{ + fr_pair_t *vp; + + for (vp = fr_pair_list_head(to_remove); + vp != NULL; + vp = fr_pair_list_next(to_remove, vp)) { + fr_pair_t *found; + + /* + * @todo - do this recursively. + */ + if (fr_type_is_structural(vp->da->type)) continue; + + for (found = fr_pair_find_by_da(list, NULL, vp->da); + found != NULL; + found = fr_pair_find_by_da(list, found, vp->da)) { + int rcode; + + rcode = fr_value_box_cmp(&vp->data, &found->data); + if (rcode != 0) continue; + + if (fr_edit_list_pair_delete(el, list, found) < 0) return -1; + break; + } + } + + return 0; +} + /** Apply operators to pairs. * * := is "if found vp, call fr_edit_list_pair_replace(). Otherwise call fr_edit_list_insert_pair_tail() @@ -1327,6 +1361,22 @@ 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_tail(el, &dst->children, src); + case T_OP_SUB_EQ: + /* + * foo -= foo --> {} + */ + if (&dst->children == src) { + fr_pair_t *vp; + + while ((vp = fr_pair_list_head(&dst->children)) != NULL) { + if (fr_edit_list_pair_delete(el, &dst->children, vp) < 0) return -1; + } + + return 0; + } + + return fr_edit_list_delete_list(el, &dst->children, src); + case T_OP_PREPEND: if (&dst->children == src) { fr_strerror_printf("Cannot prepend list to itself"); diff --git a/src/lib/util/token.c b/src/lib/util/token.c index a8f30c3289d..47fc882a319 100644 --- a/src/lib/util/token.c +++ b/src/lib/util/token.c @@ -176,6 +176,7 @@ const bool fr_assignment_op[T_TOKEN_LAST] = { const bool fr_list_assignment_op[T_TOKEN_LAST] = { T(ADD_EQ), /* append */ + T(SUB_EQ), /* remove */ T(AND_EQ), /* intersection */ T(OR_EQ), /* union */ T(LE), /* merge RHS */ diff --git a/src/tests/keywords/edit-list-remove b/src/tests/keywords/edit-list-remove index 6378ef16f99..a90140b72ff 100644 --- a/src/tests/keywords/edit-list-remove +++ b/src/tests/keywords/edit-list-remove @@ -66,5 +66,30 @@ if (&request.Tmp-String-0) { test_fail } +&request.Tmp-String-0 := { "foo", "bar", "baz" } + +# +# Remove one by value. +# +# @todo - allow for == or =~ in the RHS list, +# as a condition? For now, it's an exact match. :( +# +&request -= { + &Tmp-String-0 = "bar" +} + +if (&Tmp-String-0[0] != "foo") { + test_fail +} + +if (&Tmp-String-0[1] != "baz") { + test_fail +} + +if (&Tmp-String-0[2]) { + test_fail +} + +%(debug_attr:request[*]) success