diff --git a/lib/cfg-grammar.y b/lib/cfg-grammar.y index 361aaf8d1..59decde07 100644 --- a/lib/cfg-grammar.y +++ b/lib/cfg-grammar.y @@ -206,7 +206,7 @@ main_location_print (FILE *yyo, YYLTYPE const * const yylocp) %left ';' /* operators in the filter language, the order of this determines precedence */ -%right KW_ASSIGN 9000, KW_PLUS_ASSIGN 9001 +%right KW_ASSIGN 9000, KW_PLUS_ASSIGN 9001, KW_NULLV_ASSIGN 9002 %right '?' ':' %right KW_NULL_COALESCING %left KW_OR 9010 diff --git a/lib/cfg-lex.l b/lib/cfg-lex.l index 14de8e338..e3d476f7a 100644 --- a/lib/cfg-lex.l +++ b/lib/cfg-lex.l @@ -321,6 +321,7 @@ filterx_word [^ \#'"/\(\)\{\}\[\]\\;\r\n\t,|\.@:] =~ { return KW_REGEXP_MATCH; } !~ { return KW_REGEXP_NOMATCH; } \?\? { return KW_NULL_COALESCING; } +=\?\? { return KW_NULLV_ASSIGN; } (-|\+)?{digit}+\.{digit}+ { yylval->fnum = strtod(yytext, NULL); return LL_FLOAT; } 0x{xdigit}+ { diff --git a/lib/filterx/expr-assign.c b/lib/filterx/expr-assign.c index 04c20e4c4..e2a5d23bc 100644 --- a/lib/filterx/expr-assign.c +++ b/lib/filterx/expr-assign.c @@ -24,18 +24,15 @@ #include "filterx/object-primitive.h" #include "filterx/filterx-ref.h" #include "filterx/object-json.h" +#include "filterx/filterx-object-istype.h" +#include "filterx/filterx-eval.h" +#include "filterx/object-null.h" +#include "filterx/object-message-value.h" #include "scratch-buffers.h" -static FilterXObject * -_eval(FilterXExpr *s) +static inline FilterXObject * +_assign(FilterXBinaryOp *self, FilterXObject *value) { - FilterXBinaryOp *self = (FilterXBinaryOp *) s; - - FilterXObject *value = filterx_expr_eval(self->rhs); - - if (!value) - return NULL; - /* TODO: create ref unconditionally after implementing hierarchical CoW for JSON types * (or after creating our own dict/list repr) */ if (!value->weak_referenced) @@ -52,6 +49,59 @@ _eval(FilterXExpr *s) return value; } +static inline FilterXObject * +_suppress_error(void) +{ + msg_debug("FILTERX null coalesce assignment supressing error", filterx_format_last_error()); + filterx_eval_clear_errors(); + + return filterx_null_new(); +} + +static FilterXObject * +_nullv_assign_eval(FilterXExpr *s) +{ + FilterXBinaryOp *self = (FilterXBinaryOp *) s; + + FilterXObject *value = filterx_expr_eval(self->rhs); + + if (!value || filterx_object_is_type(value, &FILTERX_TYPE_NAME(null)) + || (filterx_object_is_type(value, &FILTERX_TYPE_NAME(message_value)) + && filterx_message_value_get_type(value) == LM_VT_NULL)) + { + if (!value) + return _suppress_error(); + + return value; + } + + return _assign(self, value); +} + +static FilterXObject * +_assign_eval(FilterXExpr *s) +{ + FilterXBinaryOp *self = (FilterXBinaryOp *) s; + + FilterXObject *value = filterx_expr_eval(self->rhs); + + if (!value) + return NULL; + + return _assign(self, value); +} + +FilterXExpr * +filterx_nullv_assign_new(FilterXExpr *lhs, FilterXExpr *rhs) +{ + FilterXBinaryOp *self = g_new0(FilterXBinaryOp, 1); + + filterx_binary_op_init_instance(self, lhs, rhs); + self->super.eval = _nullv_assign_eval; + self->super.ignore_falsy_result = TRUE; + return &self->super; +} + /* NOTE: takes the object reference */ FilterXExpr * filterx_assign_new(FilterXExpr *lhs, FilterXExpr *rhs) @@ -59,7 +109,7 @@ filterx_assign_new(FilterXExpr *lhs, FilterXExpr *rhs) FilterXBinaryOp *self = g_new0(FilterXBinaryOp, 1); filterx_binary_op_init_instance(self, lhs, rhs); - self->super.eval = _eval; + self->super.eval = _assign_eval; self->super.ignore_falsy_result = TRUE; return &self->super; } diff --git a/lib/filterx/expr-assign.h b/lib/filterx/expr-assign.h index 28d856a12..ba40faa50 100644 --- a/lib/filterx/expr-assign.h +++ b/lib/filterx/expr-assign.h @@ -26,6 +26,6 @@ #include "filterx/filterx-expr.h" FilterXExpr *filterx_assign_new(FilterXExpr *lhs, FilterXExpr *rhs); - +FilterXExpr *filterx_nullv_assign_new(FilterXExpr *lhs, FilterXExpr *rhs); #endif diff --git a/lib/filterx/expr-set-subscript.c b/lib/filterx/expr-set-subscript.c index aabecd849..341c63c7c 100644 --- a/lib/filterx/expr-set-subscript.c +++ b/lib/filterx/expr-set-subscript.c @@ -24,6 +24,10 @@ #include "filterx/object-primitive.h" #include "filterx/filterx-eval.h" #include "filterx/filterx-ref.h" +#include "filterx/filterx-object-istype.h" +#include "filterx/filterx-eval.h" +#include "filterx/object-null.h" +#include "filterx/object-message-value.h" #include "scratch-buffers.h" typedef struct _FilterXSetSubscript @@ -34,16 +38,66 @@ typedef struct _FilterXSetSubscript FilterXExpr *new_value; } FilterXSetSubscript; +static inline FilterXObject * +_set_subscript(FilterXSetSubscript *self, FilterXObject *object, FilterXObject *key, FilterXObject **new_value) +{ + if (object->readonly) + { + filterx_eval_push_error("Object set-subscript failed, object is readonly", &self->super, key); + return NULL; + } + + /* TODO: create ref unconditionally after implementing hierarchical CoW for JSON types + * (or after creating our own dict/list repr) */ + if (!(*new_value)->weak_referenced) + { + *new_value = filterx_ref_new(*new_value); + } + + FilterXObject *cloned = filterx_object_clone(*new_value); + filterx_object_unref(*new_value); + *new_value = NULL; + + if (!filterx_object_set_subscript(object, key, &cloned)) + { + filterx_eval_push_error("Object set-subscript failed", &self->super, key); + filterx_object_unref(cloned); + return NULL; + } + + return cloned; +} + +static inline FilterXObject * +_suppress_error(void) +{ + msg_debug("FILTERX null coalesce assignment supressing error", filterx_format_last_error()); + filterx_eval_clear_errors(); + + return filterx_null_new(); +} + static FilterXObject * -_eval(FilterXExpr *s) +_nullv_set_subscript_eval(FilterXExpr *s) { FilterXSetSubscript *self = (FilterXSetSubscript *) s; FilterXObject *result = NULL; - FilterXObject *new_value = NULL, *key = NULL, *object = NULL; + FilterXObject *key = NULL; + + FilterXObject *new_value = filterx_expr_eval(self->new_value); + if (!new_value || filterx_object_is_type(new_value, &FILTERX_TYPE_NAME(null)) + || (filterx_object_is_type(new_value, &FILTERX_TYPE_NAME(message_value)) + && filterx_message_value_get_type(new_value) == LM_VT_NULL)) + { + if (!new_value) + return _suppress_error(); - object = filterx_expr_eval_typed(self->object); + return new_value; + } + + FilterXObject *object = filterx_expr_eval_typed(self->object); if (!object) - return NULL; + goto exit; if (self->key) { @@ -51,43 +105,42 @@ _eval(FilterXExpr *s) if (!key) goto exit; } - else - { - /* append */ - key = NULL; - } - if (object->readonly) - { - filterx_eval_push_error("Object set-subscript failed, object is readonly", s, key); - goto exit; - } + result = _set_subscript(self, object, key, &new_value); - new_value = filterx_expr_eval(self->new_value); +exit: + filterx_object_unref(new_value); + filterx_object_unref(key); + filterx_object_unref(object); + return result; +} + +static FilterXObject * +_set_subscript_eval(FilterXExpr *s) +{ + FilterXSetSubscript *self = (FilterXSetSubscript *) s; + FilterXObject *result = NULL; + FilterXObject *key = NULL; + + FilterXObject *new_value = filterx_expr_eval(self->new_value); if (!new_value) + return NULL; + + FilterXObject *object = filterx_expr_eval_typed(self->object); + if (!object) goto exit; - /* TODO: create ref unconditionally after implementing hierarchical CoW for JSON types - * (or after creating our own dict/list repr) */ - if (!new_value->weak_referenced) + if (self->key) { - new_value = filterx_ref_new(new_value); + key = filterx_expr_eval(self->key); + if (!key) + goto exit; } - FilterXObject *cloned = filterx_object_clone(new_value); - filterx_object_unref(new_value); - - if (!filterx_object_set_subscript(object, key, &cloned)) - { - filterx_eval_push_error("Object set-subscript failed", s, key); - filterx_object_unref(cloned); - } - else - { - result = cloned; - } + result = _set_subscript(self, object, key, &new_value); exit: + filterx_object_unref(new_value); filterx_object_unref(key); filterx_object_unref(object); return result; @@ -139,13 +192,30 @@ _free(FilterXExpr *s) filterx_expr_free_method(s); } +FilterXExpr * +filterx_nullv_set_subscript_new(FilterXExpr *object, FilterXExpr *key, FilterXExpr *new_value) +{ + FilterXSetSubscript *self = g_new0(FilterXSetSubscript, 1); + + filterx_expr_init_instance(&self->super); + self->super.eval = _nullv_set_subscript_eval; + self->super.init = _init; + self->super.deinit = _deinit; + self->super.free_fn = _free; + self->object = object; + self->key = key; + self->new_value = new_value; + self->super.ignore_falsy_result = TRUE; + return &self->super; +} + FilterXExpr * filterx_set_subscript_new(FilterXExpr *object, FilterXExpr *key, FilterXExpr *new_value) { FilterXSetSubscript *self = g_new0(FilterXSetSubscript, 1); filterx_expr_init_instance(&self->super); - self->super.eval = _eval; + self->super.eval = _set_subscript_eval; self->super.init = _init; self->super.deinit = _deinit; self->super.free_fn = _free; diff --git a/lib/filterx/expr-set-subscript.h b/lib/filterx/expr-set-subscript.h index 8b2aed05b..d81b99625 100644 --- a/lib/filterx/expr-set-subscript.h +++ b/lib/filterx/expr-set-subscript.h @@ -26,6 +26,6 @@ #include "filterx/filterx-expr.h" FilterXExpr *filterx_set_subscript_new(FilterXExpr *object, FilterXExpr *key, FilterXExpr *new_value); - +FilterXExpr *filterx_nullv_set_subscript_new(FilterXExpr *object, FilterXExpr *key, FilterXExpr *new_value); #endif diff --git a/lib/filterx/expr-setattr.c b/lib/filterx/expr-setattr.c index 63ca21847..274161941 100644 --- a/lib/filterx/expr-setattr.c +++ b/lib/filterx/expr-setattr.c @@ -25,6 +25,10 @@ #include "filterx/object-string.h" #include "filterx/filterx-eval.h" #include "filterx/filterx-ref.h" +#include "filterx/filterx-object-istype.h" +#include "filterx/filterx-eval.h" +#include "filterx/object-null.h" +#include "filterx/object-message-value.h" #include "scratch-buffers.h" typedef struct _FilterXSetAttr @@ -35,47 +39,92 @@ typedef struct _FilterXSetAttr FilterXExpr *new_value; } FilterXSetAttr; -static FilterXObject * -_eval(FilterXExpr *s) +static inline FilterXObject * +_setattr(FilterXSetAttr *self, FilterXObject *object, FilterXObject **new_value) { - FilterXSetAttr *self = (FilterXSetAttr *) s; - FilterXObject *result = NULL; - - FilterXObject *object = filterx_expr_eval_typed(self->object); - if (!object) - return NULL; - if (object->readonly) { - filterx_eval_push_error("Attribute set failed, object is readonly", s, self->attr); - goto exit; + filterx_eval_push_error("Attribute set failed, object is readonly", &self->super, self->attr); + return NULL; } - FilterXObject *new_value = filterx_expr_eval(self->new_value); - if (!new_value) - goto exit; - /* TODO: create ref unconditionally after implementing hierarchical CoW for JSON types * (or after creating our own dict/list repr) */ - if (!new_value->weak_referenced) + if (!(*new_value)->weak_referenced) { - new_value = filterx_ref_new(new_value); + *new_value = filterx_ref_new(*new_value); } - FilterXObject *cloned = filterx_object_clone(new_value); - filterx_object_unref(new_value); + FilterXObject *cloned = filterx_object_clone(*new_value); + filterx_object_unref(*new_value); + *new_value = NULL; if (!filterx_object_setattr(object, self->attr, &cloned)) { - filterx_eval_push_error("Attribute set failed", s, self->attr); + filterx_eval_push_error("Attribute set failed", &self->super, self->attr); filterx_object_unref(cloned); + return NULL; } - else + + return cloned; +} + +static inline FilterXObject * +_suppress_error(void) +{ + msg_debug("FILTERX null coalesce assignment supressing error", filterx_format_last_error()); + filterx_eval_clear_errors(); + + return filterx_null_new(); +} + +static FilterXObject * +_nullv_setattr_eval(FilterXExpr *s) +{ + FilterXSetAttr *self = (FilterXSetAttr *) s; + FilterXObject *result = NULL; + + FilterXObject *new_value = filterx_expr_eval(self->new_value); + if (!new_value || filterx_object_is_type(new_value, &FILTERX_TYPE_NAME(null)) + || (filterx_object_is_type(new_value, &FILTERX_TYPE_NAME(message_value)) + && filterx_message_value_get_type(new_value) == LM_VT_NULL)) { - result = cloned; + if (!new_value) + return _suppress_error(); + + return new_value; } + FilterXObject *object = filterx_expr_eval_typed(self->object); + if (!object) + goto exit; + + result = _setattr(self, object, &new_value); + exit: + filterx_object_unref(new_value); + filterx_object_unref(object); + return result; +} + +static FilterXObject * +_setattr_eval(FilterXExpr *s) +{ + FilterXSetAttr *self = (FilterXSetAttr *) s; + FilterXObject *result = NULL; + + FilterXObject *new_value = filterx_expr_eval(self->new_value); + if (!new_value) + return NULL; + + FilterXObject *object = filterx_expr_eval_typed(self->object); + if (!object) + goto exit; + + result = _setattr(self, object, &new_value); + +exit: + filterx_object_unref(new_value); filterx_object_unref(object); return result; } @@ -118,6 +167,25 @@ _free(FilterXExpr *s) filterx_expr_free_method(s); } +FilterXExpr * +filterx_nullv_setattr_new(FilterXExpr *object, FilterXString *attr_name, FilterXExpr *new_value) +{ + FilterXSetAttr *self = g_new0(FilterXSetAttr, 1); + + filterx_expr_init_instance(&self->super); + self->super.eval = _nullv_setattr_eval; + self->super.init = _init; + self->super.deinit = _deinit; + self->super.free_fn = _free; + self->object = object; + + self->attr = (FilterXObject *) attr_name; + + self->new_value = new_value; + self->super.ignore_falsy_result = TRUE; + return &self->super; +} + /* Takes reference of object and new_value */ FilterXExpr * filterx_setattr_new(FilterXExpr *object, FilterXString *attr_name, FilterXExpr *new_value) @@ -125,7 +193,7 @@ filterx_setattr_new(FilterXExpr *object, FilterXString *attr_name, FilterXExpr * FilterXSetAttr *self = g_new0(FilterXSetAttr, 1); filterx_expr_init_instance(&self->super); - self->super.eval = _eval; + self->super.eval = _setattr_eval; self->super.init = _init; self->super.deinit = _deinit; self->super.free_fn = _free; diff --git a/lib/filterx/expr-setattr.h b/lib/filterx/expr-setattr.h index fac30396a..157fe479c 100644 --- a/lib/filterx/expr-setattr.h +++ b/lib/filterx/expr-setattr.h @@ -27,6 +27,6 @@ #include "filterx/object-string.h" FilterXExpr *filterx_setattr_new(FilterXExpr *object, FilterXString *attr_name, FilterXExpr *new_value); - +FilterXExpr *filterx_nullv_setattr_new(FilterXExpr *object, FilterXString *attr_name, FilterXExpr *new_value); #endif diff --git a/lib/filterx/filterx-grammar.ym b/lib/filterx/filterx-grammar.ym index ff581f50e..b39b5f9b8 100644 --- a/lib/filterx/filterx-grammar.ym +++ b/lib/filterx/filterx-grammar.ym @@ -116,6 +116,7 @@ construct_template_expr(LogTemplate *template) %type stmt_expr %type assignment %type plus_assignment +%type nullv_assignment %type generator_plus_assignment %type generator_assignment %type generator_casted_assignment @@ -216,9 +217,15 @@ assignment | expr '[' ']' KW_ASSIGN expr { $$ = filterx_set_subscript_new($1, NULL, $5); } | generator_assignment | plus_assignment + | nullv_assignment ; - +nullv_assignment + : variable KW_NULLV_ASSIGN expr { $$ = filterx_nullv_assign_new($1, $3); } + | expr '.' identifier KW_NULLV_ASSIGN expr { $$ = filterx_nullv_setattr_new($1, filterx_config_frozen_string(configuration, $3), $5); free($3); } + | expr '[' expr ']' KW_NULLV_ASSIGN expr { $$ = filterx_nullv_set_subscript_new($1, $3, $6); } + | expr '[' ']' KW_NULLV_ASSIGN expr { $$ = filterx_nullv_set_subscript_new($1, NULL, $5); } + ; generator_assignment /* TODO extract lvalues */ diff --git a/news/fx-feature-395.md b/news/fx-feature-395.md new file mode 100644 index 000000000..b8f385fe8 --- /dev/null +++ b/news/fx-feature-395.md @@ -0,0 +1,16 @@ +`=??` assignment operator + +Syntactic sugar operator, which could slightly improve performance as well. + +It can be used to assign a non-null value to the left-hand side. +Evaluation errors from the right-hand side will be suppressed. + +For example, + +`resource.attributes['service.name'] =?? $PROGRAM;` can be used instead of: + +``` +if (isset($PROGRAM)) { + resource.attributes['service.name'] = $PROGRAM; +}; +``` diff --git a/tests/light/functional_tests/filterx/test_filterx.py b/tests/light/functional_tests/filterx/test_filterx.py index 9908cd9fb..ddeface37 100644 --- a/tests/light/functional_tests/filterx/test_filterx.py +++ b/tests/light/functional_tests/filterx/test_filterx.py @@ -1709,6 +1709,29 @@ def test_null_coalesce_use_default_on_null(config, syslog_ng): assert file_true.read_log() == "bar\n" +def test_nullv_coalesce_assignment(config, syslog_ng): + (file_true, file_false) = create_config( + config, r""" + x = null; + y = "bar"; + obj = json(); + obj.a =?? x; + obj.b =?? y; + obj.b =?? nonexistent; + + $MSG =?? y; + $MSG =?? x; + $MSG =?? nonexistent; + $MSG += string(obj); + """, + ) + syslog_ng.start(config) + + assert file_true.get_stats()["processed"] == 1 + assert "processed" not in file_false.get_stats() + assert file_true.read_log() == 'bar{"b":"bar"}\n' + + def test_null_coalesce_use_default_on_error_and_supress_error(config, syslog_ng): (file_true, file_false) = create_config( config, r"""