From: Karel Zak Date: Tue, 12 Sep 2023 12:22:40 +0000 (+0200) Subject: libsmartcols: (filter) add ability to cast data X-Git-Tag: v2.40-rc1~151^2~68 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ea36de1223698ea16f1f5f70fba77eff63a39312;p=thirdparty%2Futil-linux.git libsmartcols: (filter) add ability to cast data * 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 --- diff --git a/libsmartcols/samples/filter.c b/libsmartcols/samples/filter.c index 5be95e4960..6567854cc1 100644 --- a/libsmartcols/samples/filter.c +++ b/libsmartcols/samples/filter.c @@ -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) diff --git a/libsmartcols/src/filter-expr.c b/libsmartcols/src/filter-expr.c index acbf09badf..f6616e5633 100644 --- a/libsmartcols/src/filter-expr.c +++ b/libsmartcols/src/filter-expr.c @@ -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; } diff --git a/libsmartcols/src/filter-param.c b/libsmartcols/src/filter-param.c index ad10ebf551..5a43c845fd 100644 --- a/libsmartcols/src/filter-param.c +++ b/libsmartcols/src/filter-param.c @@ -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) { diff --git a/libsmartcols/src/filter.c b/libsmartcols/src/filter.c index 868fdb64df..5a4957b8bb 100644 --- a/libsmartcols/src/filter.c +++ b/libsmartcols/src/filter.c @@ -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; } diff --git a/libsmartcols/src/init.c b/libsmartcols/src/init.c index cd5e05db6d..5d51691841 100644 --- a/libsmartcols/src/init.c +++ b/libsmartcols/src/init.c @@ -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 } }; diff --git a/libsmartcols/src/libsmartcols.h.in b/libsmartcols/src/libsmartcols.h.in index 200d7f5d3d..3ceca417fa 100644 --- a/libsmartcols/src/libsmartcols.h.in +++ b/libsmartcols/src/libsmartcols.h.in @@ -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 }; /* diff --git a/libsmartcols/src/smartcolsP.h b/libsmartcols/src/smartcolsP.h index 04500b238f..df3163e649 100644 --- a/libsmartcols/src/smartcolsP.h +++ b/libsmartcols/src/smartcolsP.h @@ -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);