From 90ada5c7c86cf280b2a4468e285671e9314e0d29 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Wed, 30 Jul 2014 10:48:23 +0200 Subject: [PATCH] store, frontend: Parse object field matchers. Field names are identified as ":" and all conditional operators are supported to compare them against an expression. --- src/core/store_lookup.c | 114 ++++++++++++++++++++++---------- src/frontend/grammar.y | 7 ++ src/include/core/store.h | 13 ++++ t/unit/core/store_lookup_test.c | 85 ++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 36 deletions(-) diff --git a/src/core/store_lookup.c b/src/core/store_lookup.c index c692b8c..124c451 100644 --- a/src/core/store_lookup.c +++ b/src/core/store_lookup.c @@ -873,6 +873,44 @@ sdb_store_isnull_matcher(const char *attr_name) MATCHER_ISNULL, attr_name)); } /* sdb_store_isnull_matcher */ +static sdb_store_matcher_t * +maybe_inv_matcher(sdb_store_matcher_t *m, _Bool inv) +{ + sdb_store_matcher_t *tmp; + + if ((! m) || (! inv)) + return m; + + tmp = sdb_store_inv_matcher(m); + /* pass ownership to the inverse matcher */ + sdb_object_deref(SDB_OBJ(m)); + return tmp; +} /* maybe_inv_matcher */ + +static int +parse_cond_op(const char *op, + sdb_store_matcher_t *(**matcher)(sdb_store_cond_t *), _Bool *inv) +{ + *inv = 0; + if (! strcasecmp(op, "<")) + *matcher = sdb_store_lt_matcher; + else if (! strcasecmp(op, "<=")) + *matcher = sdb_store_le_matcher; + else if (! strcasecmp(op, "=")) + *matcher = sdb_store_eq_matcher; + else if (! strcasecmp(op, ">=")) + *matcher = sdb_store_ge_matcher; + else if (! strcasecmp(op, ">")) + *matcher = sdb_store_gt_matcher; + else if (! strcasecmp(op, "!=")) { + *matcher = sdb_store_eq_matcher; + *inv = 1; + } + else + return -1; + return 0; +} /* parse_cond_op */ + static sdb_store_matcher_t * parse_attr_cmp(const char *attr, const char *op, sdb_store_expr_t *expr) { @@ -892,21 +930,7 @@ parse_attr_cmp(const char *attr, const char *op, sdb_store_expr_t *expr) } else if (! expr) return NULL; - else if (! strcasecmp(op, "<")) - matcher = sdb_store_lt_matcher; - else if (! strcasecmp(op, "<=")) - matcher = sdb_store_le_matcher; - else if (! strcasecmp(op, "=")) - matcher = sdb_store_eq_matcher; - else if (! strcasecmp(op, ">=")) - matcher = sdb_store_ge_matcher; - else if (! strcasecmp(op, ">")) - matcher = sdb_store_gt_matcher; - else if (! strcasecmp(op, "!=")) { - matcher = sdb_store_eq_matcher; - inv = 1; - } - else + else if (parse_cond_op(op, &matcher, &inv)) return NULL; cond = sdb_store_attr_cond(attr, expr); @@ -916,17 +940,7 @@ parse_attr_cmp(const char *attr, const char *op, sdb_store_expr_t *expr) m = matcher(cond); /* pass ownership to 'm' or destroy in case of an error */ sdb_object_deref(SDB_OBJ(cond)); - if (! m) - return NULL; - - if (inv) { - sdb_store_matcher_t *tmp; - tmp = sdb_store_inv_matcher(m); - /* pass ownership to the inverse matcher */ - sdb_object_deref(SDB_OBJ(m)); - m = tmp; - } - return m; + return maybe_inv_matcher(m, inv); } /* parse_attr_cmp */ sdb_store_matcher_t * @@ -984,19 +998,47 @@ sdb_store_matcher_parse_cmp(const char *obj_type, const char *attr, m = sdb_store_attr_matcher(attr, value.data.string, re); sdb_data_free_datum(&value); + return maybe_inv_matcher(m, inv); +} /* sdb_store_matcher_parse_cmp */ - if (! m) +sdb_store_matcher_t * +sdb_store_matcher_parse_field_cmp(const char *name, const char *op, + sdb_store_expr_t *expr) +{ + sdb_store_matcher_t *(*matcher)(sdb_store_cond_t *) = NULL; + sdb_store_matcher_t *m; + sdb_store_cond_t *cond; + _Bool inv = 0; + + int field; + + if (! expr) return NULL; - if (inv) { - sdb_store_matcher_t *tmp; - tmp = sdb_store_inv_matcher(m); - /* pass ownership to the inverse matcher */ - sdb_object_deref(SDB_OBJ(m)); - m = tmp; - } - return m; -} /* sdb_store_matcher_parse_cmp */ + if (! strcasecmp(name, "last_update")) + field = SDB_FIELD_LAST_UPDATE; + else if (! strcasecmp(name, "age")) + field = SDB_FIELD_AGE; + else if (! strcasecmp(name, "interval")) + field = SDB_FIELD_INTERVAL; + else if (! strcasecmp(name, "backend")) + field = SDB_FIELD_BACKEND; + else + return NULL; + + if (parse_cond_op(op, &matcher, &inv)) + return NULL; + + cond = sdb_store_obj_cond(field, expr); + if (! cond) + return NULL; + + assert(matcher); + m = matcher(cond); + /* pass ownership to 'm' or destroy in case of an error */ + sdb_object_deref(SDB_OBJ(cond)); + return maybe_inv_matcher(m, inv); +} /* sdb_store_matcher_parse_field_cmp */ sdb_store_matcher_t * sdb_store_dis_matcher(sdb_store_matcher_t *left, sdb_store_matcher_t *right) diff --git a/src/frontend/grammar.y b/src/frontend/grammar.y index 85fe8c4..5717429 100644 --- a/src/frontend/grammar.y +++ b/src/frontend/grammar.y @@ -310,6 +310,13 @@ matcher: * Parse matchers comparing object attributes with a value. */ compare_matcher: + ':' IDENTIFIER op expression + { + $$ = sdb_store_matcher_parse_field_cmp($2, $3, $4); + free($2); $2 = NULL; + sdb_object_deref(SDB_OBJ($4)); + } + | IDENTIFIER op expression { $$ = sdb_store_matcher_parse_cmp($1, NULL, $2, $3); diff --git a/src/include/core/store.h b/src/include/core/store.h index 308523a..bad17a2 100644 --- a/src/include/core/store.h +++ b/src/include/core/store.h @@ -317,6 +317,19 @@ sdb_store_matcher_t * sdb_store_matcher_parse_cmp(const char *obj_type, const char *attr, const char *op, sdb_store_expr_t *expr); +/* + * sdb_store_matcher_parse_field_cmp: + * Parse a simple compare expression for queryable object fields ( + * ). + * + * Returns: + * - a matcher object on success + * - NULL else + */ +sdb_store_matcher_t * +sdb_store_matcher_parse_field_cmp(const char *name, const char *op, + sdb_store_expr_t *expr); + /* * sdb_store_dis_matcher: * Creates a matcher matching the disjunction (logical OR) of two matchers. diff --git a/t/unit/core/store_lookup_test.c b/t/unit/core/store_lookup_test.c index 89ef451..8268883 100644 --- a/t/unit/core/store_lookup_test.c +++ b/t/unit/core/store_lookup_test.c @@ -610,6 +610,90 @@ START_TEST(test_parse_cmp) } END_TEST +START_TEST(test_parse_field_cmp) +{ + sdb_data_t datetime = { SDB_TYPE_DATETIME, { .datetime = 1 } }; + sdb_data_t string = { SDB_TYPE_STRING, { .string = "s" } }; + + struct { + const char *field; + const char *op; + const sdb_data_t *value; + int expected; + } golden_data[] = { + { "last_update", "<", &datetime, MATCHER_LT }, + { "last_update", "<=", &datetime, MATCHER_LE }, + { "last_update", "=", &datetime, MATCHER_EQ }, + { "last_update", ">=", &datetime, MATCHER_GE }, + { "last_update", ">", &datetime, MATCHER_GT }, + { "last_update", "!=", &datetime, MATCHER_NOT }, + { "age", "<", &datetime, MATCHER_LT }, + { "age", "<=", &datetime, MATCHER_LE }, + { "age", "=", &datetime, MATCHER_EQ }, + { "age", ">=", &datetime, MATCHER_GE }, + { "age", ">", &datetime, MATCHER_GT }, + { "age", "!=", &datetime, MATCHER_NOT }, + { "interval", "<", &datetime, MATCHER_LT }, + { "interval", "<=", &datetime, MATCHER_LE }, + { "interval", "=", &datetime, MATCHER_EQ }, + { "interval", ">=", &datetime, MATCHER_GE }, + { "interval", ">", &datetime, MATCHER_GT }, + { "interval", "!=", &datetime, MATCHER_NOT }, + { "backend", "=", &string, MATCHER_EQ }, + { "backend", "!=", &string, MATCHER_NOT }, + /* the behavior for other operators on :backend + * is currently unspecified */ + { "last_update", "=", NULL, -1 }, + { "last_update", "IS", NULL, -1 }, + { "age", "=", NULL, -1 }, + { "interval", "=", NULL, -1 }, + { "backend", "=", NULL, -1 }, + { "backend", "=~", &string, -1 }, + }; + + size_t i; + + for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) { + sdb_store_matcher_t *check; + sdb_store_expr_t *expr; + char buf[1024]; + + if (sdb_data_format(golden_data[i].value, + buf, sizeof(buf), SDB_UNQUOTED) < 0) + snprintf(buf, sizeof(buf), "ERR"); + + expr = sdb_store_expr_constvalue(golden_data[i].value); + fail_unless(expr != NULL || golden_data[i].value == NULL, + "sdb_store_expr_constvalue(%s) = NULL; expected: ", + buf); + + check = sdb_store_matcher_parse_field_cmp(golden_data[i].field, + golden_data[i].op, expr); + sdb_object_deref(SDB_OBJ(expr)); + + if (golden_data[i].expected == -1) { + fail_unless(check == NULL, + "sdb_store_matcher_parse_field_cmp(%s, %s, expr{%s}) = %p; " + "expected: NULL", golden_data[i].field, + golden_data[i].op, buf, check); + continue; + } + + fail_unless(check != NULL, + "sdb_store_matcher_parse_field_cmp(%s, %s, %s) = %p; " + "expected: NULL", golden_data[i].field, + golden_data[i].op, buf, check); + fail_unless(M(check)->type == golden_data[i].expected, + "sdb_store_matcher_parse_field_cmp(%s, %s, %s) returned " + "matcher of type %d; expected: %d", golden_data[i].field, + golden_data[i].op, buf, M(check)->type, + golden_data[i].expected); + + sdb_object_deref(SDB_OBJ(check)); + } +} +END_TEST + static int scan_cb(sdb_store_obj_t *obj, void *user_data) { @@ -761,6 +845,7 @@ core_store_lookup_suite(void) tcase_add_test(tc, test_obj_cond); tcase_add_test(tc, test_store_match_op); tcase_add_test(tc, test_parse_cmp); + tcase_add_test(tc, test_parse_field_cmp); tcase_add_test(tc, test_scan); suite_add_tcase(s, tc); -- 2.30.2