diff --git a/lib/filterx/expr-plus.c b/lib/filterx/expr-plus.c index 9f66b0688e..011f24ee39 100644 --- a/lib/filterx/expr-plus.c +++ b/lib/filterx/expr-plus.c @@ -35,37 +35,21 @@ _eval(FilterXExpr *s) { FilterXOperatorPlus *self = (FilterXOperatorPlus *) s; - FilterXObject *lhs_object = filterx_expr_eval(self->super.lhs); + FilterXObject *lhs_object = filterx_expr_eval_typed(self->super.lhs); if (!lhs_object) - { - return NULL; - } + return NULL; - FilterXObject *rhs_object = filterx_expr_eval(self->super.rhs); + FilterXObject *rhs_object = filterx_expr_eval_typed(self->super.rhs); if (!rhs_object) { filterx_object_unref(lhs_object); return NULL; } - if (filterx_object_is_type(lhs_object, &FILTERX_TYPE_NAME(string)) && - filterx_object_is_type(rhs_object, &FILTERX_TYPE_NAME(string))) - { - gsize lhs_len, rhs_len; - const gchar *lhs_value = filterx_string_get_value(lhs_object, &lhs_len); - const gchar *rhs_value = filterx_string_get_value(rhs_object, &rhs_len); - GString *buffer = scratch_buffers_alloc(); - - g_string_append_len(buffer, lhs_value, lhs_len); - g_string_append_len(buffer, rhs_value, rhs_len); - /* FIXME: support taking over the already allocated space */ - return filterx_string_new(buffer->str, buffer->len); - } - - filterx_eval_push_error("operator+ only works on strings", s, NULL); + FilterXObject *res = filterx_object_add_object(lhs_object, rhs_object); filterx_object_unref(lhs_object); filterx_object_unref(rhs_object); - return NULL; + return res; } FilterXExpr * diff --git a/lib/filterx/filterx-object.c b/lib/filterx/filterx-object.c index bbb0c440c1..5b29d51e55 100644 --- a/lib/filterx/filterx-object.c +++ b/lib/filterx/filterx-object.c @@ -78,6 +78,7 @@ filterx_type_init(FilterXType *type) INIT_TYPE_METHOD(type, dict_factory); INIT_TYPE_METHOD(type, repr); INIT_TYPE_METHOD(type, len); + INIT_TYPE_METHOD(type, add); INIT_TYPE_METHOD(type, free_fn); if (!filterx_type_register(type->name, type)) diff --git a/lib/filterx/filterx-object.h b/lib/filterx/filterx-object.h index 6fe5069b36..5d738d2dad 100644 --- a/lib/filterx/filterx-object.h +++ b/lib/filterx/filterx-object.h @@ -51,6 +51,7 @@ struct _FilterXType FilterXObject *(*dict_factory)(void); gboolean (*repr)(FilterXObject *self, GString *repr); gboolean (*len)(FilterXObject *self, guint64 *len); + FilterXObject *(*add)(FilterXObject *self, FilterXObject *object); void (*free_fn)(FilterXObject *self); }; @@ -293,4 +294,17 @@ filterx_object_create_dict(FilterXObject *self) return self->type->dict_factory(); } +static inline FilterXObject * +filterx_object_add_object(FilterXObject *self, FilterXObject *object) +{ + if (!self->type->add) + { + msg_error("The add method is not supported for the given type", + evt_tag_str("type", self->type->name)); + return NULL; + } + + return self->type->add(self, object); +} + #endif diff --git a/lib/filterx/object-datetime.c b/lib/filterx/object-datetime.c index 1fbd444ce9..dad47bf180 100644 --- a/lib/filterx/object-datetime.c +++ b/lib/filterx/object-datetime.c @@ -205,6 +205,28 @@ _repr(FilterXObject *s, GString *repr) return datetime_repr(&ut, repr); } +static FilterXObject * +_add(FilterXObject *self, FilterXObject *object) +{ + UnixTime result; + UnixTime base = filterx_datetime_get_value(self); + if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(integer))) + { + GenericNumber gn = filterx_primitive_get_value(object); + result = unix_time_add_duration(base, gn_as_int64(&gn)); + } + else if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(double))) + { + GenericNumber gn = filterx_primitive_get_value(object); + result = unix_time_add_duration(base, (gint64)(gn_as_double(&gn) * USEC_PER_SEC)); + } + else + return NULL; + + + return filterx_datetime_new(&result); +} + const gchar * _strptime_get_time_str_from_object(FilterXObject *obj, gsize *len) { @@ -381,4 +403,5 @@ FILTERX_DEFINE_TYPE(datetime, FILTERX_TYPE_NAME(object), .map_to_json = _map_to_json, .marshal = _marshal, .repr = _repr, + .add = _add, ); diff --git a/lib/filterx/object-dict-interface.c b/lib/filterx/object-dict-interface.c index 4fff5614e3..3f3479f8ae 100644 --- a/lib/filterx/object-dict-interface.c +++ b/lib/filterx/object-dict-interface.c @@ -207,6 +207,24 @@ filterx_dict_init_instance(FilterXDict *self, FilterXType *type) self->support_attr = TRUE; } +static FilterXObject * +_add(FilterXObject *lhs_object, FilterXObject *rhs_object) +{ + if (!filterx_object_is_type(lhs_object, &FILTERX_TYPE_NAME(dict)) || + !filterx_object_is_type(rhs_object, &FILTERX_TYPE_NAME(dict))) + return NULL; + + FilterXObject *cloned = filterx_object_clone(lhs_object); + + if (!filterx_dict_merge(cloned, rhs_object)) + goto error; + + return cloned; +error: + filterx_object_unref(cloned); + return NULL; +} + FILTERX_DEFINE_TYPE(dict, FILTERX_TYPE_NAME(object), .is_mutable = TRUE, .len = _len, @@ -217,4 +235,5 @@ FILTERX_DEFINE_TYPE(dict, FILTERX_TYPE_NAME(object), .getattr = _getattr, .setattr = _setattr, .map_to_json = _map_to_json, + .add = _add, ); diff --git a/lib/filterx/object-list-interface.c b/lib/filterx/object-list-interface.c index bd759a915c..497ca0e99c 100644 --- a/lib/filterx/object-list-interface.c +++ b/lib/filterx/object-list-interface.c @@ -298,6 +298,24 @@ filterx_list_init_instance(FilterXList *self, FilterXType *type) filterx_object_init_instance(&self->super, type); } +static FilterXObject * +_add(FilterXObject *lhs_object, FilterXObject *rhs_object) +{ + if (!filterx_object_is_type(lhs_object, &FILTERX_TYPE_NAME(list)) || + !filterx_object_is_type(rhs_object, &FILTERX_TYPE_NAME(list))) + return NULL; + + FilterXObject *cloned = filterx_object_clone(lhs_object); + + if(!filterx_list_merge(cloned, rhs_object)) + goto error; + + return cloned; +error: + filterx_object_unref(cloned); + return NULL; +} + FILTERX_DEFINE_TYPE(list, FILTERX_TYPE_NAME(object), .is_mutable = TRUE, .len = _len, @@ -306,4 +324,5 @@ FILTERX_DEFINE_TYPE(list, FILTERX_TYPE_NAME(object), .is_key_set = _is_key_set, .unset_key = _unset_key, .map_to_json = _map_to_json, + .add = _add, ); diff --git a/lib/filterx/object-primitive.c b/lib/filterx/object-primitive.c index 8884e0d14d..da6e2adb20 100644 --- a/lib/filterx/object-primitive.c +++ b/lib/filterx/object-primitive.c @@ -63,6 +63,23 @@ _integer_map_to_json(FilterXObject *s, struct json_object **object, FilterXObjec return TRUE; } +static FilterXObject * +_integer_add(FilterXObject *self, FilterXObject *object) +{ + GenericNumber base = filterx_primitive_get_value(self); + if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(integer))) + { + GenericNumber add = filterx_primitive_get_value(object); + return filterx_integer_new(gn_as_int64(&base) + gn_as_int64(&add)); + } + if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(double))) + { + GenericNumber add = filterx_primitive_get_value(object); + return filterx_double_new(gn_as_int64(&base) + gn_as_double(&add)); + } + return NULL; +} + gboolean integer_repr(gint64 val, GString *repr) { @@ -103,6 +120,23 @@ _double_map_to_json(FilterXObject *s, struct json_object **object, FilterXObject return TRUE; } +static FilterXObject * +_double_add(FilterXObject *self, FilterXObject *object) +{ + GenericNumber base = filterx_primitive_get_value(self); + if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(integer))) + { + GenericNumber add = filterx_primitive_get_value(object); + return filterx_double_new(gn_as_double(&base) + gn_as_int64(&add)); + } + else if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(double))) + { + GenericNumber add = filterx_primitive_get_value(object); + return filterx_double_new(gn_as_double(&base) + gn_as_double(&add)); + } + return NULL; +} + gboolean double_repr(double val, GString *repr) { @@ -326,6 +360,7 @@ FILTERX_DEFINE_TYPE(integer, FILTERX_TYPE_NAME(object), .marshal = _integer_marshal, .map_to_json = _integer_map_to_json, .repr = _repr, + .add = _integer_add, ); FILTERX_DEFINE_TYPE(double, FILTERX_TYPE_NAME(object), @@ -333,6 +368,7 @@ FILTERX_DEFINE_TYPE(double, FILTERX_TYPE_NAME(object), .marshal = _double_marshal, .map_to_json = _double_map_to_json, .repr = _repr, + .add = _double_add, ); FILTERX_DEFINE_TYPE(boolean, FILTERX_TYPE_NAME(object), diff --git a/lib/filterx/object-string.c b/lib/filterx/object-string.c index 8ccdc26ee2..5491078a6b 100644 --- a/lib/filterx/object-string.c +++ b/lib/filterx/object-string.c @@ -120,6 +120,26 @@ _string_repr(FilterXObject *s, GString *repr) return TRUE; } +static FilterXObject * +_string_add(FilterXObject *self, FilterXObject *object) +{ + + if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(string))) + { + gsize lhs_len, rhs_len; + const gchar *lhs_value = filterx_string_get_value(self, &lhs_len); + const gchar *rhs_value = filterx_string_get_value(object, &rhs_len); + GString *buffer = scratch_buffers_alloc(); + + g_string_append_len(buffer, lhs_value, lhs_len); + g_string_append_len(buffer, rhs_value, rhs_len); + /* FIXME: support taking over the already allocated space */ + return filterx_string_new(buffer->str, buffer->len); + } + + return NULL; +} + FilterXObject * filterx_string_new(const gchar *str, gssize str_len) { @@ -190,6 +210,26 @@ _bytes_repr(FilterXObject *s, GString *repr) return TRUE; } +static FilterXObject * +_bytes_add(FilterXObject *self, FilterXObject *object) +{ + + if (filterx_object_is_type(object, &FILTERX_TYPE_NAME(bytes))) + { + gsize lhs_len, rhs_len; + const gchar *lhs_value = filterx_bytes_get_value(self, &lhs_len); + const gchar *rhs_value = filterx_bytes_get_value(object, &rhs_len); + GString *buffer = scratch_buffers_alloc(); + + g_string_append_len(buffer, lhs_value, lhs_len); + g_string_append_len(buffer, rhs_value, rhs_len); + /* FIXME: support taking over the already allocated space */ + return filterx_bytes_new(buffer->str, buffer->len); + } + + return NULL; +} + FilterXObject * filterx_bytes_new(const gchar *mem, gssize mem_len) { @@ -294,7 +334,6 @@ filterx_typecast_protobuf(FilterXExpr *s, GPtrArray *args) return NULL; } - /* these types are independent type-wise but share a lot of the details */ FILTERX_DEFINE_TYPE(string, FILTERX_TYPE_NAME(object), @@ -303,6 +342,7 @@ FILTERX_DEFINE_TYPE(string, FILTERX_TYPE_NAME(object), .map_to_json = _map_to_json, .truthy = _truthy, .repr = _string_repr, + .add = _string_add, ); FILTERX_DEFINE_TYPE(bytes, FILTERX_TYPE_NAME(object), @@ -311,6 +351,7 @@ FILTERX_DEFINE_TYPE(bytes, FILTERX_TYPE_NAME(object), .map_to_json = _bytes_map_to_json, .truthy = _truthy, .repr = _bytes_repr, + .add = _bytes_add, ); FILTERX_DEFINE_TYPE(protobuf, FILTERX_TYPE_NAME(object), diff --git a/lib/filterx/tests/CMakeLists.txt b/lib/filterx/tests/CMakeLists.txt index e044e55a30..d7b06e4fa4 100644 --- a/lib/filterx/tests/CMakeLists.txt +++ b/lib/filterx/tests/CMakeLists.txt @@ -20,3 +20,4 @@ add_unit_test(LIBTEST CRITERION TARGET test_func_unset_empties DEPENDS json-plug add_unit_test(LIBTEST CRITERION TARGET test_expr_function DEPENDS json-plugin ${JSONC_LIBRARY}) add_unit_test(LIBTEST CRITERION TARGET test_expr_regexp DEPENDS json-plugin ${JSONC_LIBRARY}) add_unit_test(LIBTEST CRITERION TARGET test_expr_null_coalesce DEPENDS json-plugin ${JSONC_LIBRARY}) +add_unit_test(LIBTEST CRITERION TARGET test_expr_plus DEPENDS json-plugin ${JSONC_LIBRARY}) diff --git a/lib/filterx/tests/Makefile.am b/lib/filterx/tests/Makefile.am index d036b11ec9..57da802a0c 100644 --- a/lib/filterx/tests/Makefile.am +++ b/lib/filterx/tests/Makefile.am @@ -21,7 +21,8 @@ lib_filterx_tests_TESTS = \ lib/filterx/tests/test_func_istype \ lib/filterx/tests/test_func_unset_empties \ lib/filterx/tests/test_expr_regexp \ - lib/filterx/tests/test_expr_null_coalesce + lib/filterx/tests/test_expr_null_coalesce \ + lib/filterx/tests/test_expr_plus EXTRA_DIST += lib/filterx/tests/CMakeLists.txt @@ -92,3 +93,6 @@ lib_filterx_tests_test_expr_regexp_LDADD = $(TEST_LDADD) $(JSON_LIBS) lib_filterx_tests_test_expr_null_coalesce_CFLAGS = $(TEST_CFLAGS) lib_filterx_tests_test_expr_null_coalesce_LDADD = $(TEST_LDADD) $(JSON_LIBS) + +lib_filterx_tests_test_expr_plus_CFLAGS = $(TEST_CFLAGS) +lib_filterx_tests_test_expr_plus_LDADD = $(TEST_LDADD) $(JSON_LIBS) diff --git a/lib/filterx/tests/test_expr_plus.c b/lib/filterx/tests/test_expr_plus.c new file mode 100644 index 0000000000..81661f37e9 --- /dev/null +++ b/lib/filterx/tests/test_expr_plus.c @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 shifter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include +#include "libtest/cr_template.h" +#include "libtest/filterx-lib.h" + +#include "filterx/filterx-object.h" +#include "filterx/object-primitive.h" +#include "filterx/expr-comparison.h" +#include "filterx/filterx-expr.h" +#include "filterx/expr-literal.h" +#include "filterx/object-string.h" +#include "filterx/object-null.h" +#include "filterx/object-datetime.h" +#include "filterx/object-message-value.h" +#include "filterx/expr-null-coalesce.h" +#include "filterx/func-istype.h" +#include "filterx/filterx-eval.h" +#include "filterx/func-len.h" +#include "filterx/expr-function.h" +#include "filterx/expr-plus.h" +#include "filterx/object-json.h" +#include "filterx/object-list-interface.h" +#include "filterx/object-dict-interface.h" + +#include "apphook.h" +#include "scratch-buffers.h" + +Test(expr_plus, test_string_success) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_string_new("foo", -1)); + FilterXExpr *rhs = filterx_literal_new(filterx_string_new("bar", -1)); + + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(string))); + + + gsize size; + const gchar *res = filterx_string_get_value(obj, &size); + + cr_assert_str_eq(res, "foobar"); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_string_add_wrong_type) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_string_new("foo", -1)); + FilterXExpr *rhs = filterx_literal_new(filterx_null_new()); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_null(obj); + filterx_expr_unref(expr); +} + +#define TEST_EPOCH 1577836800000000 // 2020-01-01T00:00:00 in usec + +Test(expr_plus, test_datetime_add_datetime) +{ + UnixTime lhs_time = unix_time_from_unix_epoch(TEST_EPOCH); + UnixTime rhs_time = unix_time_from_unix_epoch(3600000000); // 1 h + + FilterXExpr *lhs = filterx_literal_new(filterx_datetime_new(&lhs_time)); + FilterXExpr *rhs = filterx_literal_new(filterx_datetime_new(&rhs_time)); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_null(obj); // datetime + datetime operation is not supported currently + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_datetime_add_integer) +{ + UnixTime lhs_time = unix_time_from_unix_epoch(TEST_EPOCH); + + FilterXExpr *lhs = filterx_literal_new(filterx_datetime_new(&lhs_time)); + FilterXExpr *rhs = filterx_literal_new(filterx_integer_new(3600000000)); // 1h in usec + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(datetime))); + + GString *date_repr = scratch_buffers_alloc(); + cr_assert(filterx_object_repr(obj, date_repr)); + + cr_assert_str_eq(date_repr->str, "2020-01-01T01:00:00.000+00:00"); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_datetime_add_double) +{ + UnixTime lhs_time = unix_time_from_unix_epoch(TEST_EPOCH); + + FilterXExpr *lhs = filterx_literal_new(filterx_datetime_new(&lhs_time)); + FilterXExpr *rhs = filterx_literal_new(filterx_double_new(3600.0)); // 1h in sec + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(datetime))); + + GString *date_repr = scratch_buffers_alloc(); + cr_assert(filterx_object_repr(obj, date_repr)); + + cr_assert_str_eq(date_repr->str, "2020-01-01T01:00:00.000+00:00"); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_datetime_add_wrong_type) +{ + UnixTime lhs_time = unix_time_from_unix_epoch(TEST_EPOCH); + FilterXExpr *lhs = filterx_literal_new(filterx_datetime_new(&lhs_time)); + FilterXExpr *rhs = filterx_literal_new(filterx_null_new()); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_null(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_integer_add_integer) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_integer_new(33)); + FilterXExpr *rhs = filterx_literal_new(filterx_integer_new(66)); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(integer))); + + GenericNumber gn = filterx_primitive_get_value(obj); + + cr_assert_eq(gn_as_int64(&gn), 99); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_integer_add_double) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_integer_new(33)); + FilterXExpr *rhs = filterx_literal_new(filterx_double_new(0.66)); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(double))); + + GenericNumber gn = filterx_primitive_get_value(obj); + GenericNumber expected; + gn_set_double(&expected, 33 + .66, 0); + + cr_assert(gn_compare(&expected, &gn) == 0); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_integer_add_wrong_type) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_integer_new(33)); + FilterXExpr *rhs = filterx_literal_new(filterx_null_new()); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_null(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_double_add_double) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_double_new(.6)); + FilterXExpr *rhs = filterx_literal_new(filterx_double_new(3.1415)); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(double))); + + GenericNumber gn = filterx_primitive_get_value(obj); + GenericNumber expected; + gn_set_double(&expected, 3.1415 + .6, 0); + + cr_assert(gn_compare(&expected, &gn) == 0); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_double_add_integer) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_double_new(0.66)); + FilterXExpr *rhs = filterx_literal_new(filterx_integer_new(33)); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(double))); + + GenericNumber gn = filterx_primitive_get_value(obj); + GenericNumber expected; + gn_set_double(&expected, .66 + 33, 0); + + cr_assert(gn_compare(&expected, &gn) == 0); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + + +Test(expr_plus, test_double_add_wrong_type) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_double_new(.5)); + FilterXExpr *rhs = filterx_literal_new(filterx_null_new()); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_null(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_list_add_list) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_json_array_new_from_repr("[\"foo\",\"bar\"]", -1)); + FilterXExpr *rhs = filterx_literal_new(filterx_json_array_new_from_repr("[\"tik\",\"tak\"]", -1)); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(list))); + + GString *repr = scratch_buffers_alloc(); + cr_assert(filterx_object_repr(obj, repr)); + + cr_assert_str_eq(repr->str, "[\"foo\",\"bar\",\"tik\",\"tak\"]"); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + + +Test(expr_plus, test_list_add_wrong_type) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_json_array_new_from_repr("[\"foo\",\"bar\"]", -1)); + FilterXExpr *rhs = filterx_literal_new(filterx_null_new()); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_null(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_dict_add_dict) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_json_object_new_from_repr("{\"foo\":\"bar\"}", -1)); + FilterXExpr *rhs = filterx_literal_new(filterx_json_object_new_from_repr("{\"tik\":\"tak\"}", -1)); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_not_null(obj); + cr_assert(filterx_object_is_type(obj, &FILTERX_TYPE_NAME(dict))); + + GString *repr = scratch_buffers_alloc(); + cr_assert(filterx_object_repr(obj, repr)); + + cr_assert_str_eq(repr->str, "{\"foo\":\"bar\",\"tik\":\"tak\"}"); + + filterx_object_unref(obj); + filterx_expr_unref(expr); +} + +Test(expr_plus, test_dict_add_wrong_type) +{ + FilterXExpr *lhs = filterx_literal_new(filterx_json_object_new_from_repr("{\"foo\":\"bar\"}", -1)); + FilterXExpr *rhs = filterx_literal_new(filterx_null_new()); + + FilterXExpr *expr = filterx_operator_plus_new(lhs, rhs); + cr_assert_not_null(expr); + + FilterXObject *obj = filterx_expr_eval(expr); + cr_assert_null(obj); + filterx_expr_unref(expr); +} + + +static void +setup(void) +{ + app_startup(); + init_libtest_filterx(); +} + +static void +teardown(void) +{ + scratch_buffers_explicit_gc(); + deinit_libtest_filterx(); + app_shutdown(); +} + + +TestSuite(expr_plus, .init = setup, .fini = teardown); diff --git a/lib/filterx/tests/test_object_datetime.c b/lib/filterx/tests/test_object_datetime.c index 7e62b5cf73..72ed938b13 100644 --- a/lib/filterx/tests/test_object_datetime.c +++ b/lib/filterx/tests/test_object_datetime.c @@ -170,6 +170,18 @@ Test(filterx_datetime, test_filterx_datetime_typecast_from_datetime) filterx_object_unref(obj); } +Test(filterx_datetime, test_filterx_datetime_repr_method) +{ + UnixTime ut = unix_time_from_unix_epoch(3600000000); + cr_assert(ut.ut_gmtoff == 0); + cr_assert(ut.ut_usec == 0); + cr_assert(ut.ut_sec == 3600); + GString *repr = scratch_buffers_alloc(); + cr_assert(datetime_repr(&ut, repr)); + cr_assert_str_eq(repr->str, "1970-01-01T01:00:00.000+00:00"); +} + + Test(filterx_datetime, test_filterx_datetime_repr) { GPtrArray *args = g_ptr_array_new_with_free_func((GDestroyNotify) filterx_object_unref); diff --git a/lib/timeutils/unixtime.c b/lib/timeutils/unixtime.c index 3e0ce2b9ae..591c40826a 100644 --- a/lib/timeutils/unixtime.c +++ b/lib/timeutils/unixtime.c @@ -351,20 +351,41 @@ timeval_from_unix_time(UnixTime *ut) return tv; } +void +_unix_time_parts_from_unix_epoch(guint64 unix_epoch, gint64 *secs, guint32 *usecs) +{ + *secs = (gint64)(unix_epoch / USEC_PER_SEC); + *usecs = (guint32)(unix_epoch % USEC_PER_SEC); +} + UnixTime unix_time_from_unix_epoch(guint64 unix_epoch) { - UnixTime ut = - { - .ut_sec = (int64_t)(unix_epoch / USEC_PER_SEC), - .ut_usec = (guint32)(unix_epoch % USEC_PER_SEC), - .ut_gmtoff = 0, - }; + UnixTime ut = UNIX_TIME_INIT; + ut.ut_gmtoff = 0; + _unix_time_parts_from_unix_epoch(unix_epoch, &ut.ut_sec, &ut.ut_usec); return ut; } guint64 unix_time_to_unix_epoch(const UnixTime ut) { - return (guint64)((ut.ut_sec + ut.ut_gmtoff) * USEC_PER_SEC + ut.ut_usec); + gint32 gmtoff = (ut.ut_gmtoff != - 1) ? ut.ut_gmtoff : 0; + return (guint64)((ut.ut_sec + gmtoff) * USEC_PER_SEC + ut.ut_usec); +} + +UnixTime +unix_time_add_duration(UnixTime time, guint64 duration) +{ + gint64 secs; + guint32 usecs; + _unix_time_parts_from_unix_epoch(duration, &secs, &usecs); + UnixTime ut = + { + .ut_sec = time.ut_sec + secs + ((time.ut_usec + usecs) / USEC_PER_SEC), + .ut_usec = (time.ut_usec + usecs) % USEC_PER_SEC, + .ut_gmtoff = time.ut_gmtoff, + }; + return ut; + } diff --git a/lib/timeutils/unixtime.h b/lib/timeutils/unixtime.h index 90bee987e5..5e2836fcc6 100644 --- a/lib/timeutils/unixtime.h +++ b/lib/timeutils/unixtime.h @@ -85,5 +85,6 @@ gint64 unix_time_diff_in_msec(const UnixTime *a, const UnixTime *b); struct timeval timeval_from_unix_time(UnixTime *ut); UnixTime unix_time_from_unix_epoch(guint64 unix_epoch); guint64 unix_time_to_unix_epoch(const UnixTime ut); +UnixTime unix_time_add_duration(UnixTime time, guint64 duration); #endif diff --git a/tests/light/functional_tests/filterx/test_filterx.py b/tests/light/functional_tests/filterx/test_filterx.py index 2a54a76cc3..d2065a1f00 100644 --- a/tests/light/functional_tests/filterx/test_filterx.py +++ b/tests/light/functional_tests/filterx/test_filterx.py @@ -1767,3 +1767,42 @@ def test_regexp_subst_all_args_are_mandatory(config, syslog_ng): ) with pytest.raises(Exception): syslog_ng.start(config) + + +def test_add_operator_for_base_types(config, syslog_ng): + (file_true, file_false) = create_config( + config, r""" + $MSG = json(); + $MSG.string = "foo" + "bar" + "baz"; + $MSG.bytes = string(bytes("\xCA") + bytes("\xFE")); + $MSG.datetime_integer = string(strptime("2000-01-01T00:00:00Z", "%Y-%m-%dT%H:%M:%S%z") + 3600000000); + $MSG.datetime_double = string(strptime("2000-01-01T00:00:00Z", "%Y-%m-%dT%H:%M:%S%z") + 3600.0); + $MSG.integer_integer = 3 + 4 + 5; + $MSG.integer_double = 3 + 0.5; + $MSG.double_integer = 3.5 + 2; + $MSG.double_double = 3.14 + 0.86; + js1 = json_array(["foo","bar"]); + js2 = json_array(["baz","other"]); + $MSG.list_list = js1 + js2; + dict1 = json({"foo":"bar"}); + dict2 = json({"baz":"other"}); + $MSG.dict_dict = dict1 + dict2; + """, + ) + syslog_ng.start(config) + + assert file_true.get_stats()["processed"] == 1 + assert "processed" not in file_false.get_stats() + exp = ( + r"""{"string":"foobarbaz",""" + r""""bytes":"cafe",""" + r""""datetime_integer":"2000-01-01T01:00:00.000+00:00",""" + r""""datetime_double":"2000-01-01T01:00:00.000+00:00",""" + r""""integer_integer":12,""" + r""""integer_double":3.5,""" + r""""double_integer":5.5,""" + r""""double_double":4.0,""" + r""""list_list":["foo","bar","baz","other"],""" + r""""dict_dict":{"foo":"bar","baz":"other"}}""" + "\n" + ) + assert file_true.read_log() == exp