Code

data: Added sdb_data_expr_eval().
authorSebastian Harl <sh@tokkee.org>
Sun, 27 Jul 2014 21:06:03 +0000 (23:06 +0200)
committerSebastian Harl <sh@tokkee.org>
Sun, 27 Jul 2014 21:06:03 +0000 (23:06 +0200)
This function may be used to evaluate simple operation expressions on data
values. Supported operators are +, -, *, /, %, and (for string and binary
values) concatenation.

src/Makefile.am
src/core/data.c
src/include/core/data.h
t/unit/core/data_test.c

index 0130d4269abaee37b5a177d9b98779c85fe4b088..2adc2e0c2a1e8290638ac7f5f7f4b260ebe40ca6 100644 (file)
@@ -89,7 +89,7 @@ libsysdb_la_SOURCES = \
                utils/unixsock.c include/utils/unixsock.h
 libsysdb_la_CFLAGS = $(AM_CFLAGS)
 libsysdb_la_CPPFLAGS = $(AM_CPPFLAGS) $(LTDLINCL)
-libsysdb_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:0:0 -pthread
+libsysdb_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:0:0 -pthread -lm
 libsysdb_la_LIBADD = libsysdb_fe_parser.la \
                $(LIBLTDL) -lrt liboconfig/liboconfig.la
 libsysdb_la_DEPENDENCIES = libsysdb_fe_parser.la liboconfig/liboconfig.la
@@ -124,7 +124,7 @@ sysdbd_SOURCES = tools/sysdbd/main.c include/sysdb.h \
                $(libsysdb_la_SOURCES)
 sysdbd_CFLAGS = $(AM_CFLAGS) -DBUILD_DATE="\"$$( date --utc '+%F %T' ) (UTC)\""
 sysdbd_CPPFLAGS = $(AM_CPPFLAGS) $(LTDLINCL)
-sysdbd_LDFLAGS = $(AM_LDFLAGS) -export-dynamic -pthread
+sysdbd_LDFLAGS = $(AM_LDFLAGS) -export-dynamic -pthread -lm
 sysdbd_LDADD = libsysdb_fe_parser.la liboconfig/liboconfig.la \
                $(LIBLTDL) -lrt
 sysdbd_DEPENDENCIES = libsysdb_fe_parser.la liboconfig/liboconfig.la
index 2381636092ceb03b7d4ef25ae8c9935ec3300583..cdafac30f284701482b583bd2f14f85e508b6526 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 
+#include <math.h>
+
+/*
+ * private helper functions
+ */
+
+static int
+data_concat(const sdb_data_t *d1, const sdb_data_t *d2, sdb_data_t *res)
+{
+       unsigned char *new;
+       unsigned char *s1, *s2;
+       size_t len1, len2;
+
+       if (d1->type == SDB_TYPE_STRING) {
+               s1 = (unsigned char *)d1->data.string;
+               s2 = (unsigned char *)d2->data.string;
+               len1 = s1 ? strlen((char *)s1) : 0;
+               len2 = s2 ? strlen((char *)s2) : 0;
+       }
+       else if (d1->type == SDB_TYPE_BINARY) {
+               s1 = d1->data.binary.datum;
+               s2 = d2->data.binary.datum;
+               len1 = d1->data.binary.length;
+               len2 = d2->data.binary.length;
+       }
+       else
+               return -1;
+
+       if (s1 || s2) {
+               new = malloc(len1 + len2 + 1);
+               if (! new)
+                       return -1;
+       }
+       else
+               new = NULL;
+
+       if (len1)
+               memcpy(new, s1, len1);
+       if (len2)
+               memcpy(new + len1, s2, len2);
+       if (new)
+               new[len1 + len2] = '\0';
+
+       res->type = d1->type;
+       if (res->type == SDB_TYPE_STRING) {
+               res->data.string = (char *)new;
+       }
+       else {
+               res->data.binary.datum = new;
+               res->data.binary.length = len1 + len2;
+       }
+       return 0;
+} /* data_concat */
+
 /*
  * public API
  */
@@ -150,6 +204,99 @@ sdb_data_cmp(const sdb_data_t *d1, const sdb_data_t *d2)
 #undef CMP_NULL
 } /* sdb_data_cmp */
 
+int
+sdb_data_expr_eval(int op, const sdb_data_t *d1, const sdb_data_t *d2,
+               sdb_data_t *res)
+{
+       if (d1->type != d2->type)
+               return -1;
+
+       switch (op) {
+               case SDB_DATA_CONCAT:
+                       return data_concat(d1, d2, res);
+               case SDB_DATA_ADD:
+                       switch (d1->type) {
+                               case SDB_TYPE_INTEGER:
+                                       res->data.integer = d1->data.integer + d2->data.integer;
+                                       break;
+                               case SDB_TYPE_DECIMAL:
+                                       res->data.decimal = d1->data.decimal + d2->data.decimal;
+                                       break;
+                               case SDB_TYPE_DATETIME:
+                                       res->data.datetime = d1->data.datetime + d2->data.datetime;
+                                       break;
+                               default:
+                                       return -1;
+                       }
+                       break;
+               case SDB_DATA_SUB:
+                       switch (d1->type) {
+                               case SDB_TYPE_INTEGER:
+                                       res->data.integer = d1->data.integer - d2->data.integer;
+                                       break;
+                               case SDB_TYPE_DECIMAL:
+                                       res->data.decimal = d1->data.decimal - d2->data.decimal;
+                                       break;
+                               case SDB_TYPE_DATETIME:
+                                       res->data.datetime = d1->data.datetime - d2->data.datetime;
+                                       break;
+                               default:
+                                       return -1;
+                       }
+                       break;
+               case SDB_DATA_MUL:
+                       switch (d1->type) {
+                               case SDB_TYPE_INTEGER:
+                                       res->data.integer = d1->data.integer * d2->data.integer;
+                                       break;
+                               case SDB_TYPE_DECIMAL:
+                                       res->data.decimal = d1->data.decimal * d2->data.decimal;
+                                       break;
+                               case SDB_TYPE_DATETIME:
+                                       res->data.datetime = d1->data.datetime * d2->data.datetime;
+                                       break;
+                               default:
+                                       return -1;
+                       }
+                       break;
+               case SDB_DATA_DIV:
+                       switch (d1->type) {
+                               case SDB_TYPE_INTEGER:
+                                       res->data.integer = d1->data.integer / d2->data.integer;
+                                       break;
+                               case SDB_TYPE_DECIMAL:
+                                       res->data.decimal = d1->data.decimal / d2->data.decimal;
+                                       break;
+                               case SDB_TYPE_DATETIME:
+                                       res->data.datetime = d1->data.datetime / d2->data.datetime;
+                                       break;
+                               default:
+                                       return -1;
+                       }
+                       break;
+               case SDB_DATA_MOD:
+                       switch (d1->type) {
+                               case SDB_TYPE_INTEGER:
+                                       res->data.integer = d1->data.integer % d2->data.integer;
+                                       break;
+                               case SDB_TYPE_DECIMAL:
+                                       res->data.decimal = fmod(d1->data.decimal, d2->data.decimal);
+                                       break;
+                               case SDB_TYPE_DATETIME:
+                                       res->data.datetime = d1->data.datetime % d2->data.datetime;
+                                       break;
+                               default:
+                                       return -1;
+                       }
+                       break;
+               default:
+                       return -1;
+       }
+
+       res->type = d1->type;
+       return 0;
+} /* sdb_data_expr_eval */
+
 size_t
 sdb_data_strlen(const sdb_data_t *datum)
 {
index 4d7fda31c11d590a865fc6aff0665292249f0014..2e9b90bcfd55af660d3cb576b1c5e3e21427730e 100644 (file)
@@ -75,6 +75,7 @@ typedef struct {
                } binary;             /* SDB_TYPE_BINARY */
        } data;
 } sdb_data_t;
+#define SDB_DATA_INIT { 0, { .integer = 0 } }
 
 /*
  * sdb_data_copy:
@@ -114,6 +115,46 @@ sdb_data_free_datum(sdb_data_t *datum);
 int
 sdb_data_cmp(const sdb_data_t *d1, const sdb_data_t *d2);
 
+/*
+ * Operators supported by sdb_data_eval_expr.
+ */
+enum {
+       SDB_DATA_ADD = 1, /* addition */
+       SDB_DATA_SUB,     /* substraction */
+       SDB_DATA_MUL,     /* multiplication */
+       SDB_DATA_DIV,     /* division */
+       SDB_DATA_MOD,     /* modulo */
+       SDB_DATA_CONCAT,  /* string / binary data concatenation */
+};
+
+#define SDB_DATA_OP_TO_STRING(op) \
+       (((op) == SDB_DATA_ADD) \
+               ? "+" \
+               : ((op) == SDB_DATA_SUB) \
+                       ? "-" \
+                       : ((op) == SDB_DATA_MUL) \
+                               ? "*" \
+                               : ((op) == SDB_DATA_DIV) \
+                                       ? "/" \
+                                       : ((op) == SDB_DATA_MOD) \
+                                               ? "%" \
+                                               : ((op) == SDB_DATA_CONCAT) \
+                                                       ? "||" : "UNKNOWN")
+
+/*
+ * sdb_data_expr_eval:
+ * Evaluate a simple arithmetic expression on two data points. The data-type
+ * of d1 and d2 have to be the same. String and binary data only support
+ * concatenation and all other data types only support the other operators.
+ *
+ * Returns:
+ *  - 0 on success
+ *  - a negative value else
+ */
+int
+sdb_data_expr_eval(int op, const sdb_data_t *d1, const sdb_data_t *d2,
+               sdb_data_t *res);
+
 /*
  * sdb_data_strlen:
  * Returns a (worst-case) estimate for the number of bytes required to format
index e490a383e5b740f9875a0080abf4fd100da54132..0fd61a57539ff7e322be0356666a5375add663ee 100644 (file)
@@ -290,6 +290,192 @@ START_TEST(test_cmp)
 }
 END_TEST
 
+START_TEST(test_expr_eval)
+{
+       struct {
+               sdb_data_t d1;
+               sdb_data_t d2;
+               sdb_data_t expected_add;
+               sdb_data_t expected_sub;
+               sdb_data_t expected_mul;
+               sdb_data_t expected_div;
+               sdb_data_t expected_mod;
+               sdb_data_t expected_concat;
+       } golden_data[] = {
+               {
+                       { SDB_TYPE_INTEGER, { .integer = 4711 } },
+                       { SDB_TYPE_INTEGER, { .integer = 47 } },
+                       { SDB_TYPE_INTEGER, { .integer = 4758 } },
+                       { SDB_TYPE_INTEGER, { .integer = 4664 } },
+                       { SDB_TYPE_INTEGER, { .integer = 221417 } },
+                       { SDB_TYPE_INTEGER, { .integer = 100 } },
+                       { SDB_TYPE_INTEGER, { .integer = 11 } },
+                       SDB_DATA_INIT,
+               },
+               {
+                       { SDB_TYPE_DECIMAL, { .decimal = 35.0 } },
+                       { SDB_TYPE_DECIMAL, { .decimal = 17.5 } },
+                       { SDB_TYPE_DECIMAL, { .decimal = 52.5 } },
+                       { SDB_TYPE_DECIMAL, { .decimal = 17.5 } },
+                       { SDB_TYPE_DECIMAL, { .decimal = 612.5 } },
+                       { SDB_TYPE_DECIMAL, { .decimal = 2.0 } },
+                       { SDB_TYPE_DECIMAL, { .decimal = 0.0 } },
+                       SDB_DATA_INIT,
+               },
+               {
+                       { SDB_TYPE_STRING, { .string = NULL } },
+                       { SDB_TYPE_STRING, { .string = "" } },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       { SDB_TYPE_STRING, { .string = "" } },
+               },
+               {
+                       { SDB_TYPE_STRING, { .string = NULL } },
+                       { SDB_TYPE_STRING, { .string = NULL } },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       { SDB_TYPE_STRING, { .string = NULL } },
+               },
+               {
+                       { SDB_TYPE_STRING, { .string = "" } },
+                       { SDB_TYPE_STRING, { .string = NULL } },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       { SDB_TYPE_STRING, { .string = "" } },
+               },
+               {
+                       { SDB_TYPE_STRING, { .string = "a" } },
+                       { SDB_TYPE_STRING, { .string = "b" } },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       { SDB_TYPE_STRING, { .string = "ab" } },
+               },
+               {
+                       { SDB_TYPE_DATETIME, { .datetime = 47114711 } },
+                       { SDB_TYPE_DATETIME, { .datetime = 4711 } },
+                       { SDB_TYPE_DATETIME, { .datetime = 47119422 } },
+                       { SDB_TYPE_DATETIME, { .datetime = 47110000 } },
+                       { SDB_TYPE_DATETIME, { .datetime = 221957403521 } },
+                       { SDB_TYPE_DATETIME, { .datetime = 10001 } },
+                       { SDB_TYPE_DATETIME, { .datetime = 0 } },
+                       SDB_DATA_INIT,
+               },
+               {
+                       { SDB_TYPE_BINARY, { .binary = { 0, NULL } } },
+                       { SDB_TYPE_BINARY, { .binary = { 1, (unsigned char *)"a" } } },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       { SDB_TYPE_BINARY, { .binary = { 1, (unsigned char *)"a" } } },
+               },
+               {
+                       { SDB_TYPE_BINARY, { .binary = { 0, NULL } } },
+                       { SDB_TYPE_BINARY, { .binary = { 0, NULL } } },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       { SDB_TYPE_BINARY, { .binary = { 0, NULL } } },
+               },
+               {
+                       { SDB_TYPE_BINARY, { .binary = { 1, (unsigned char *)"a" } } },
+                       { SDB_TYPE_BINARY, { .binary = { 0, NULL } } },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       { SDB_TYPE_BINARY, { .binary = { 1, (unsigned char *)"a" } } },
+               },
+               {
+                       {
+                               SDB_TYPE_BINARY,
+                               { .binary = { 3, (unsigned char *)"a\0a" } },
+                       },
+                       {
+                               SDB_TYPE_BINARY,
+                               { .binary = { 3, (unsigned char *)"b\0b" } },
+                       },
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       SDB_DATA_INIT,
+                       {
+                               SDB_TYPE_BINARY,
+                               { .binary = { 6, (unsigned char *)"a\0ab\0b" } },
+                       },
+               },
+       };
+
+       size_t i;
+
+       for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
+               struct {
+                       int op;
+                       sdb_data_t expected;
+               } tests[] = {
+                       { SDB_DATA_ADD, golden_data[i].expected_add },
+                       { SDB_DATA_SUB, golden_data[i].expected_sub },
+                       { SDB_DATA_MUL, golden_data[i].expected_mul },
+                       { SDB_DATA_DIV, golden_data[i].expected_div },
+                       { SDB_DATA_MOD, golden_data[i].expected_mod },
+                       { SDB_DATA_CONCAT, golden_data[i].expected_concat },
+               };
+
+               size_t j;
+               for (j = 0; j < SDB_STATIC_ARRAY_LEN(tests); ++j) {
+                       sdb_data_t res;
+                       int check;
+
+                       char d1_str[64] = "", d2_str[64] = "";
+                       sdb_data_format(&golden_data[i].d1, d1_str, sizeof(d1_str),
+                                       SDB_DOUBLE_QUOTED);
+                       sdb_data_format(&golden_data[i].d2, d2_str, sizeof(d2_str),
+                                       SDB_DOUBLE_QUOTED);
+
+                       check = sdb_data_expr_eval(tests[j].op,
+                                       &golden_data[i].d1, &golden_data[i].d2, &res);
+                       fail_unless((check == 0) == (tests[j].expected.type != 0),
+                                       "sdb_data_expr_eval(%s, %s, %s) = %d; expected: %d",
+                                       SDB_DATA_OP_TO_STRING(tests[j].op), d1_str, d2_str, check,
+                                       tests[j].expected.type == 0 ? -1 : 0);
+                       if (tests[j].expected.type == 0)
+                               continue;
+
+                       check = sdb_data_cmp(&res, &tests[j].expected);
+                       if (check != 0) {
+                               char res_str[64] = "", expected_str[64] = "";
+                               sdb_data_format(&res, res_str, sizeof(res_str),
+                                               SDB_DOUBLE_QUOTED);
+                               sdb_data_format(&tests[j].expected, expected_str,
+                                               sizeof(expected_str), SDB_DOUBLE_QUOTED);
+                               fail("sdb_data_expr_eval(%s, %s, %s) evaluated to %s; "
+                                               "expected: %s", SDB_DATA_OP_TO_STRING(tests[j].op),
+                                               d1_str, d2_str, res_str, expected_str);
+                       }
+
+                       sdb_data_free_datum(&res);
+               }
+       }
+}
+END_TEST
+
 START_TEST(test_format)
 {
        struct {
@@ -428,6 +614,7 @@ core_data_suite(void)
        tc = tcase_create("core");
        tcase_add_test(tc, test_data);
        tcase_add_test(tc, test_cmp);
+       tcase_add_test(tc, test_expr_eval);
        tcase_add_test(tc, test_format);
        tcase_add_test(tc, test_parse);
        suite_add_tcase(s, tc);