]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libsmartcols: (filter) add ability to cast data
authorKarel Zak <kzak@redhat.com>
Tue, 12 Sep 2023 12:22:40 +0000 (14:22 +0200)
committerKarel Zak <kzak@redhat.com>
Mon, 20 Nov 2023 21:25:46 +0000 (22:25 +0100)
* add FLOAT type for JSON

  JSON standard does not care and everything is number, but for libsmartcols
  it would be better to differentiate between the types.

* add functions to cast data in the filter param

* cast data from scols rows

* cast data in the filter expression if necessary

Signed-off-by: Karel Zak <kzak@redhat.com>
libsmartcols/samples/filter.c
libsmartcols/src/filter-expr.c
libsmartcols/src/filter-param.c
libsmartcols/src/filter.c
libsmartcols/src/init.c
libsmartcols/src/libsmartcols.h.in
libsmartcols/src/smartcolsP.h

index 5be95e49603e0f87ca9c80fd2c7b79e406da6162..6567854cc11f67644107c82cfafb858303dbd39e 100644 (file)
@@ -41,7 +41,7 @@ static void setup_columns(struct libscols_table *tb)
        col = scols_table_new_column(tb, "FLOAT", 0, 0);
        if (!col)
                goto fail;
-       scols_column_set_json_type(col, SCOLS_JSON_NUMBER);
+       scols_column_set_json_type(col, SCOLS_JSON_FLOAT);
 
        col = scols_table_new_column(tb, "STRING", 0, 0);
        if (!col)
index acbf09badf89552f09a68c30ce669136ea4657cd..f6616e5633227369f5c5fddb8b9f49bd9bdf5c44 100644 (file)
@@ -86,12 +86,85 @@ void filter_dump_expr(struct ul_jsonwrt *json, struct filter_expr *n)
        ul_jsonwrt_object_close(json);
 }
 
+static int cast_node(struct libscols_filter *fltr,
+                    struct libscols_line *ln,
+                    enum filter_data type,
+                    struct filter_node *n,
+                    struct filter_param **result)
+{
+       struct filter_node *pr;
+       int status = 0, rc;
+       bool x;
+
+       switch (n->type) {
+       case F_NODE_EXPR:
+               /* convert expression to a boolean param */
+               rc = filter_eval_expr(fltr, ln, (struct filter_expr *) n, &status);
+               if (rc)
+                       return rc;
+               x = status != 0 ? true : false;
+               pr = filter_new_param(NULL, F_DATA_BOOLEAN, 0, (void *) &x);
+               if (!pr)
+                       return -ENOMEM;
+               rc = filter_cast_param(fltr, ln, type, (struct filter_param *) pr, result);
+               filter_unref_node(pr);
+               break;
+       case F_NODE_PARAM:
+               rc = filter_cast_param(fltr, ln, type, (struct filter_param *) n, result);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return rc;
+}
+
+static enum filter_data node_get_datatype(struct filter_node *n)
+{
+       switch (n->type) {
+       case F_NODE_EXPR:
+               return F_DATA_BOOLEAN;
+       case F_NODE_PARAM:
+               return ((struct filter_param *) n)->type;
+       }
+       return F_DATA_NONE;
+}
+
+static enum filter_data guess_expr_datatype(struct filter_expr *n)
+{
+       enum filter_data type;
+       enum filter_data l = node_get_datatype(n->left),
+                        r = node_get_datatype(n->right);
+
+       if (l == r)
+               type = l;
+       else {
+               bool l_holder, r_holder;
+
+               /* for expression like "FOO > 5.5" preffer type defined by a real param
+                * rather than by holder (FOO) */
+               l_holder = is_filter_holder_param(n->left);
+               r_holder = is_filter_holder_param(n->right);
+
+               if (l_holder && !r_holder)
+                       type = r;
+               else if (r_holder && !l_holder)
+                       type = l;
+               else
+                       type = l;
+       }
+
+       DBG(FPARAM, ul_debugobj(n, " expr datatype: %d", type));
+       return type;
+}
+
 int filter_eval_expr(struct libscols_filter *fltr, struct libscols_line *ln,
                        struct filter_expr *n, int *status)
 {
        int rc = 0;
        struct filter_param *l = NULL, *r = NULL;
        enum filter_etype oper = n->type;
+       enum filter_data type;
 
        /* logical operators */
        switch (oper) {
@@ -114,10 +187,16 @@ int filter_eval_expr(struct libscols_filter *fltr, struct libscols_line *ln,
                break;
        }
 
+       type = guess_expr_datatype(n);
+
        /* compare data */
-       l = (struct filter_param *) n->left;
-       r = (struct filter_param *) n->right;
-       rc = filter_compare_params(fltr, ln, oper, l, r, status);
+       rc = cast_node(fltr, ln, type, n->left, &l);
+       if (!rc)
+               rc = cast_node(fltr, ln, type, n->right, &r);
+       if (!rc)
+               rc = filter_compare_params(fltr, ln, oper, l, r, status);
 
+       filter_unref_node((struct filter_node *) l);
+       filter_unref_node((struct filter_node *) r);
        return rc;
 }
index ad10ebf5516bf501de2cf468451264826d019e94..5a43c845fd4f46622ce296127dc38b66f2646b79 100644 (file)
@@ -5,10 +5,25 @@
 
 #include "smartcolsP.h"
 
+static int cast_param(enum filter_data type, struct filter_param *n);
+
+static inline const char *datatype2str(enum filter_data type)
+{
+       static const char *types[] = {
+               [F_DATA_NONE] = "none",
+               [F_DATA_STRING] = "string",
+               [F_DATA_NUMBER] = "number",
+               [F_DATA_FLOAT] = "float",
+               [F_DATA_BOOLEAN] = "boolean"
+       };
+       return types[type];
+}
 static int param_set_data(struct filter_param *n, enum filter_data type, const void *data)
 {
        const char *p;
 
+       /*DBG(FPARAM, ul_debugobj(n, " set %s data", datatype2str(type)));*/
+
        switch (type) {
        case F_DATA_STRING:
                p = data;
@@ -67,11 +82,18 @@ struct filter_node *filter_new_param(
                break;
        }
 
-       list_add_tail(&n->pr_params, &fltr->params);
+       if (fltr)
+               list_add_tail(&n->pr_params, &fltr->params);
 
        return (struct filter_node *) n;
 }
 
+static struct filter_param *copy_param(struct filter_param *n)
+{
+       return (struct filter_param *) filter_new_param(NULL,
+                               n->type, F_HOLDER_NONE, (void *) &n->val);
+}
+
 static void param_reset_data(struct filter_param *n)
 {
        if (n->type == F_DATA_STRING)
@@ -134,26 +156,60 @@ void filter_dump_param(struct ul_jsonwrt *json, struct filter_param *n)
        ul_jsonwrt_object_close(json);
 }
 
-static int fetch_holder_data(struct libscols_filter *fltr,
+int filter_param_reset_holder(struct filter_param *n)
+{
+       if (!n->holder)
+               return 0;
+       if (!n->col)
+               return -EINVAL;
+
+       param_reset_data(n);
+
+       if (n->type != F_DATA_NONE)
+               return 0; /* already set */
+
+       switch (n->col->json_type) {
+       case SCOLS_JSON_NUMBER:
+               n->type = F_DATA_NUMBER;
+               break;
+       case SCOLS_JSON_BOOLEAN:
+               n->type = F_DATA_BOOLEAN;
+               break;
+       case SCOLS_JSON_FLOAT:
+               n->type = F_DATA_FLOAT;
+               break;
+       case SCOLS_JSON_STRING:
+       default:
+               n->type = F_DATA_STRING;
+               break;
+       }
+
+       DBG(FPARAM, ul_debugobj(n, "holder %s type: %s", n->holder_name, datatype2str(n->type)));
+       return 0;
+}
+
+static int fetch_holder_data(struct libscols_filter *fltr __attribute__((__unused__)),
                        struct filter_param *n, struct libscols_line *ln)
 {
        const char *data;
+       enum filter_data type = n->type;
        int rc;
 
-       if (n->holder != F_HOLDER_COLUMN)
+       if (n->has_value || n->holder != F_HOLDER_COLUMN)
                return 0;
        if (!n->col) {
-               DBG(FLTR, ul_debugobj(fltr, "no column for %s holder", n->holder_name));
+               DBG(FPARAM, ul_debugobj(n, "no column for %s holder", n->holder_name));
                return -EINVAL;
        }
+       DBG(FPARAM, ul_debugobj(n, "fetching %s data", n->holder_name));
 
-       DBG(FLTR, ul_debugobj(fltr, "fetching %s data", n->holder_name));
-
-       param_reset_data(n);
-
+       /* read column data, use it as string */
        data = scols_line_get_column_data(ln, n->col);
        rc = param_set_data(n, F_DATA_STRING, data);
 
+       /* cast to the wanted type */
+       if (rc == 0 && type != F_DATA_NONE)
+               rc = cast_param(type, n);
        return rc;
 }
 
@@ -166,9 +222,7 @@ int filter_eval_param(struct libscols_filter *fltr,
 
        DBG(FLTR, ul_debugobj(fltr, "eval param"));
 
-       if (!n->has_value && n->holder)
-               rc = fetch_holder_data(fltr, n, ln);
-
+       rc = fetch_holder_data(fltr, n, ln);
        if (!n->has_value || rc) {
                *status = 0;
                goto done;
@@ -309,6 +363,8 @@ static int bool_opers(enum filter_etype oper, struct filter_param *l,
        return 0;
 }
 
+/* call filter_cast_param() to be sure that param data are ready (fetched from
+ * holder, etc.) */
 int filter_compare_params(struct libscols_filter *fltr __attribute__((__unused__)),
                          struct libscols_line *ln __attribute__((__unused__)),
                          enum filter_etype oper,
@@ -344,6 +400,193 @@ int filter_compare_params(struct libscols_filter *fltr __attribute__((__unused__
        return rc;
 }
 
+static int string_cast(enum filter_data type, struct filter_param *n)
+{
+       char *str = n->val.str;
+
+       if (type == F_DATA_STRING)
+               return 0;
+
+       n->val.str = NULL;
+
+       switch (type) {
+       case F_DATA_NUMBER:
+       {
+               uint64_t num;
+               int rc = ul_strtou64(str, &num, 10);
+               if (rc)
+                       return rc;
+               n->val.num = num;
+               break;
+       }
+       case F_DATA_FLOAT:
+       {
+               long double num;
+               int rc = ul_strtold(str, &num);
+               if (rc)
+                       return rc;
+               n->val.fnum = num;
+               break;
+       }
+       case F_DATA_BOOLEAN:
+       {
+               bool x = (!str || !*str
+                              || strcasecmp(str, "false") == 0
+                              || strcasecmp(str, "0") == 0) ? false : true;
+               n->val.boolean = x;
+               break;
+       }
+       default:
+               return -EINVAL;
+       }
+
+       free(str);
+       return 0;
+}
+
+static int number_cast(enum filter_data type, struct filter_param *n)
+{
+       unsigned long long num = n->val.num;
+
+       switch (type) {
+       case F_DATA_STRING:
+               n->val.str = NULL;
+               if (asprintf(&n->val.str, "%llu", num) <= 0)
+                       return -ENOMEM;
+               break;
+       case F_DATA_NUMBER:
+               break;
+       case F_DATA_FLOAT:
+               n->val.fnum = num;
+               break;
+       case F_DATA_BOOLEAN:
+               n->val.boolean = num > 0 ? true : false;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int float_cast(enum filter_data type, struct filter_param *n)
+{
+       long double fnum = n->val.fnum;
+
+       switch (type) {
+       case F_DATA_STRING:
+               n->val.str = NULL;
+               if (asprintf(&n->val.str, "%Lg", fnum) <= 0)
+                       return -ENOMEM;
+               break;
+       case F_DATA_NUMBER:
+               n->val.num = fnum;
+               break;
+       case F_DATA_FLOAT:
+               break;;
+       case F_DATA_BOOLEAN:
+               n->val.boolean = fnum > 0.0 ? true : false;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int bool_cast(enum filter_data type, struct filter_param *n)
+{
+       bool x = n->val.boolean;
+
+       switch (type) {
+       case F_DATA_STRING:
+               n->val.str = NULL;
+               if (asprintf(&n->val.str, "%s", x ? "true" : "false") <= 0)
+                       return -ENOMEM;
+               break;
+       case F_DATA_NUMBER:
+               n->val.num = x ? 1 : 0;
+               break;
+       case F_DATA_FLOAT:
+               n->val.fnum = x ? 1.0 : 0.0;
+               break;
+       case F_DATA_BOOLEAN:
+               break;;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int cast_param(enum filter_data type, struct filter_param *n)
+{
+       int rc;
+       enum filter_data orgtype = n->type;
+
+       if (!n->has_value)
+               return -EINVAL;
+       if (type == orgtype)
+               return 0;
+
+       if (orgtype == F_DATA_STRING)
+               DBG(FPARAM, ul_debugobj(n, " casting \"%s\" to %s", n->val.str, datatype2str(type)));
+       else
+               DBG(FPARAM, ul_debugobj(n, " casting %s to %s", datatype2str(orgtype), datatype2str(type)));
+
+       switch (orgtype) {
+       case F_DATA_STRING:
+               rc = string_cast(type, n);
+               break;
+       case F_DATA_NUMBER:
+               rc = number_cast(type, n);
+               break;
+       case F_DATA_FLOAT:
+               rc = float_cast(type, n);
+               break;
+       case F_DATA_BOOLEAN:
+               rc = bool_cast(type, n);
+               break;
+       default:
+               rc = -EINVAL;
+               break;
+       }
+
+       if (rc == 0)
+               n->type = type;
+
+       if (rc)
+               DBG(FPARAM, ul_debugobj(n, "cast done [rc=%d]", rc));
+       return rc;
+}
+
+int filter_cast_param(struct libscols_filter *fltr,
+                     struct libscols_line *ln,
+                     enum filter_data type,
+                     struct filter_param *n,
+                     struct filter_param **result)
+{
+       int rc;
+       enum filter_data orgtype = n->type;
+
+       rc = fetch_holder_data(fltr, n, ln);
+       if (rc)
+               return rc;
+       if (!n->has_value)
+               return -EINVAL;
+
+       if (type == orgtype) {
+               filter_ref_node((struct filter_node *) n);      /* caller wants to call filter_unref_node() for the result */
+               *result = n;
+               return 0;
+       }
+
+       *result = copy_param(n);
+       if (!*result)
+               return -ENOMEM;
+       rc = cast_param(type, *result);
+
+       DBG(FPARAM, ul_debugobj(n, "cast done [rc=%d]", rc));
+       return rc;
+}
+
 int filter_next_param(struct libscols_filter *fltr,
                      struct libscols_iter *itr, struct filter_param **prm)
 {
index 868fdb64df3ac247a224ca3f3978f74c7908956b..5a4957b8bb6dde40e61f76a1617f327996b83807 100644 (file)
@@ -212,11 +212,21 @@ int scols_line_apply_filter(struct libscols_line *ln,
                        struct libscols_filter *fltr, int *status)
 {
        int rc;
+       struct libscols_iter itr;
+       struct filter_param *prm = NULL;
+
        if (!ln || !fltr || !fltr->root)
                return -EINVAL;
 
+       /* reset column data and types stored in the filter */
+       scols_reset_iter(&itr, SCOLS_ITER_FORWARD);
+       while (filter_next_param(fltr, &itr, &prm) == 0) {
+               if (prm->col)
+                       filter_param_reset_holder(prm);
+       }
+
        rc = filter_eval_node(fltr, ln, fltr->root, status);
 
-       DBG(FLTR, ul_debugobj(fltr, "filter applid [rc=%d, status=%d]", rc, *status));
+       DBG(FLTR, ul_debugobj(fltr, "filter done [rc=%d, status=%d]", rc, *status));
        return rc;
 }
index cd5e05db6d23a7720d151a935c83854ff4a444d4..5d51691841dfea7b7221f1049388015bbe910d0b 100644 (file)
@@ -29,6 +29,7 @@ UL_DEBUG_DEFINE_MASKNAMES(libsmartcols) =
        { "line", SCOLS_DEBUG_LINE,     "table line utils" },
        { "tab", SCOLS_DEBUG_TAB,       "table utils" },
        { "filter", SCOLS_DEBUG_FLTR,   "lines filter" },
+       { "fparam", SCOLS_DEBUG_FPARAM, "filter params" },
        { NULL, 0, NULL }
 };
 
index 200d7f5d3dbe24bd88bddeb87ec4df528870fcef..3ceca417fa0ac55498544cbc28e35baaf071c327 100644 (file)
@@ -104,6 +104,7 @@ enum {
        SCOLS_JSON_ARRAY_STRING = 3,        /* e.g. for multi-line (SCOLS_FL_WRAP) cells */
        SCOLS_JSON_ARRAY_NUMBER = 4,
        SCOLS_JSON_BOOLEAN_OPTIONAL = 5,
+       SCOLS_JSON_FLOAT     = 6
 };
 
 /*
index 04500b238f1e6a518a6f176f8bc742b4ab9ae913..df3163e649201c8a4702e7af5537e09fe10c8eaf 100644 (file)
@@ -35,6 +35,7 @@
 #define SCOLS_DEBUG_BUFF       (1 << 6)
 #define SCOLS_DEBUG_GROUP      (1 << 7)
 #define SCOLS_DEBUG_FLTR       (1 << 8)
+#define SCOLS_DEBUG_FPARAM     (1 << 9)
 #define SCOLS_DEBUG_ALL                0xFFFF
 
 UL_DEBUG_DECLARE_MASK(libsmartcols);
@@ -575,6 +576,7 @@ void filter_dump_param(struct ul_jsonwrt *json, struct filter_param *n);
 int filter_eval_param(struct libscols_filter *fltr, struct libscols_line *ln,
                        struct filter_param *n, int *status);
 void filter_free_param(struct filter_param *n);
+int filter_param_reset_holder(struct filter_param *n);
 
 int filter_next_param(struct libscols_filter *fltr,
                         struct libscols_iter *itr, struct filter_param **prm);
@@ -585,6 +587,16 @@ int filter_compare_params(struct libscols_filter *fltr,
                           struct filter_param *l,
                           struct filter_param *r,
                           int *status);
+int filter_cast_param(struct libscols_filter *fltr,
+                      struct libscols_line *ln,
+                      enum filter_data type,
+                      struct filter_param *n,
+                      struct filter_param **result);
+
+#define is_filter_holder_param(_n) \
+                       (filter_node_get_type(_n) == F_NODE_PARAM \
+                        && ((struct filter_param *)(_n))->holder)
+
 
 /* expr */
 void filter_free_expr(struct filter_expr *n);