Code

store, frontend: Added support for matching NULL attributes.
authorSebastian Harl <sh@tokkee.org>
Sun, 20 Jul 2014 18:55:44 +0000 (20:55 +0200)
committerSebastian Harl <sh@tokkee.org>
Sun, 20 Jul 2014 18:55:44 +0000 (20:55 +0200)
This matches any hosts which are missing the specified attribute.

src/core/store-private.h
src/core/store_lookup.c
src/frontend/grammar.y
src/frontend/scanner.l
src/include/core/store.h
t/unit/frontend/parser_test.c

index c7d99f9b910ee2abee60b4318c198db39f8ace1e..a2121f45c211dad50003007e6a438f9958725638 100644 (file)
@@ -123,6 +123,7 @@ enum {
        MATCHER_EQ,
        MATCHER_GE,
        MATCHER_GT,
+       MATCHER_ISNULL,
 };
 
 #define MATCHER_SYM(t) \
@@ -136,6 +137,7 @@ enum {
                : ((t) == MATCHER_EQ) ? "=" \
                : ((t) == MATCHER_GE) ? ">=" \
                : ((t) == MATCHER_GT) ? ">" \
+               : ((t) == MATCHER_ISNULL) ? "IS NULL" \
                : "UNKNOWN")
 
 /* match the name of something */
@@ -188,6 +190,12 @@ typedef struct {
 } attr_matcher_t;
 #define ATTR_M(m) ((attr_matcher_t *)(m))
 
+typedef struct {
+       sdb_store_matcher_t super;
+       char *attr_name; /* we only support matching attributes */
+} isnull_matcher_t;
+#define ISNULL_M(m) ((isnull_matcher_t *)(m))
+
 /* match using conditionals */
 typedef struct {
        sdb_store_matcher_t super;
index 7e2acefd12208c5d276dc73fdef3569a48cc45e2..557b93662f55d0ce4a83329112c98a2382b74a85 100644 (file)
@@ -254,11 +254,19 @@ match_gt(sdb_store_matcher_t *m, sdb_host_t *host)
        return (status != INT_MAX) && (status > 0);
 } /* match_gt */
 
+static int
+match_isnull(sdb_store_matcher_t *m, sdb_host_t *host)
+{
+       assert(m->type == MATCHER_ISNULL);
+       return attr_get(host, ISNULL_M(m)->attr_name) == NULL;
+} /* match_isnull */
+
 typedef int (*matcher_cb)(sdb_store_matcher_t *, sdb_host_t *);
 
 /* this array needs to be indexable by the matcher types;
  * -> update the enum in store-private.h when updating this */
-static matcher_cb matchers[] = {
+static matcher_cb
+matchers[] = {
        match_logical,
        match_logical,
        match_unary,
@@ -269,6 +277,7 @@ static matcher_cb matchers[] = {
        match_eq,
        match_ge,
        match_gt,
+       match_isnull,
 };
 
 /*
@@ -542,6 +551,39 @@ uop_tostring(sdb_store_matcher_t *m, char *buf, size_t buflen)
        return buf;
 } /* uop_tostring */
 
+static int
+isnull_matcher_init(sdb_object_t *obj, va_list ap)
+{
+       const char *name;
+
+       M(obj)->type = va_arg(ap, int);
+       if (M(obj)->type != MATCHER_ISNULL)
+               return -1;
+
+       name = va_arg(ap, const char *);
+       if (! name)
+               return -1;
+       ISNULL_M(obj)->attr_name = strdup(name);
+       if (! ISNULL_M(obj)->attr_name)
+               return -1;
+       return 0;
+} /* isnull_matcher_init */
+
+static void
+isnull_matcher_destroy(sdb_object_t *obj)
+{
+       if (ISNULL_M(obj)->attr_name)
+               free(ISNULL_M(obj)->attr_name);
+       ISNULL_M(obj)->attr_name = NULL;
+} /* isnull_matcher_destroy */
+
+static char *
+isnull_tostring(sdb_store_matcher_t *m, char *buf, size_t buflen)
+{
+       snprintf(buf, buflen, "(IS NULL, attr.%s)", ISNULL_M(m)->attr_name);
+       return buf;
+} /* isnull_tostring */
+
 static sdb_type_t name_type = {
        /* size = */ sizeof(name_matcher_t),
        /* init = */ name_matcher_init,
@@ -572,11 +614,18 @@ static sdb_type_t uop_type = {
        /* destroy = */ uop_matcher_destroy,
 };
 
+static sdb_type_t isnull_type = {
+       /* size = */ sizeof(isnull_matcher_t),
+       /* init = */ isnull_matcher_init,
+       /* destroy = */ isnull_matcher_destroy,
+};
+
 typedef char *(*matcher_tostring_cb)(sdb_store_matcher_t *, char *, size_t);
 
 /* this array needs to be indexable by the matcher types;
  * -> update the enum in store-private.h when updating this */
-static matcher_tostring_cb matchers_tostring[] = {
+static matcher_tostring_cb
+matchers_tostring[] = {
        op_tostring,
        op_tostring,
        uop_tostring,
@@ -587,6 +636,7 @@ static matcher_tostring_cb matchers_tostring[] = {
        cond_tostring,
        cond_tostring,
        cond_tostring,
+       isnull_tostring,
 };
 
 /*
@@ -667,6 +717,13 @@ sdb_store_gt_matcher(sdb_store_cond_t *cond)
                                MATCHER_GT, cond));
 } /* sdb_store_gt_matcher */
 
+sdb_store_matcher_t *
+sdb_store_isnull_matcher(const char *attr_name)
+{
+       return M(sdb_object_create("isnull-matcher", isnull_type,
+                               MATCHER_ISNULL, attr_name));
+} /* sdb_store_isnull_matcher */
+
 static sdb_store_matcher_t *
 parse_attr_cmp(const char *attr, const char *op, const sdb_data_t *value)
 {
@@ -680,7 +737,11 @@ parse_attr_cmp(const char *attr, const char *op, const sdb_data_t *value)
        if (! strcasecmp(attr, "name"))
                return NULL;
 
-       if (! strcasecmp(op, "<"))
+       if (! strcasecmp(op, "IS"))
+               return sdb_store_isnull_matcher(attr);
+       else if (! value)
+               return NULL;
+       else if (! strcasecmp(op, "<"))
                matcher = sdb_store_lt_matcher;
        else if (! strcasecmp(op, "<="))
                matcher = sdb_store_le_matcher;
index 34a9c6b6474502d9fc059df45a6ed93a79890567..2173e6df1b383f1ce2ce58f849cb70c37aa021c2 100644 (file)
@@ -81,10 +81,13 @@ sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
 
 %token SCANNER_ERROR
 
-%token AND OR NOT WHERE
+%token AND OR IS NOT WHERE
 %token CMP_EQUAL CMP_NEQUAL CMP_REGEX CMP_NREGEX
 %token CMP_LT CMP_LE CMP_GE CMP_GT
 
+/* NULL token */
+%token NULL_T
+
 %token FETCH LIST LOOKUP
 
 %token <str> IDENTIFIER STRING
@@ -97,7 +100,8 @@ sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
 %right NOT
 %left CMP_EQUAL CMP_NEQUAL
 %left CMP_LT CMP_LE CMP_GE CMP_GT
-%left CMP_REGEX CMP_NREGEX
+%nonassoc CMP_REGEX CMP_NREGEX
+%nonassoc IS
 %left '(' ')'
 %left '.'
 
@@ -305,6 +309,25 @@ compare_matcher:
                        free($3); $3 = NULL;
                        sdb_data_free_datum(&$5);
                }
+       |
+       IDENTIFIER '.' IDENTIFIER IS NULL_T
+               {
+                       $$ = sdb_store_matcher_parse_cmp($1, $3, "IS", NULL);
+                       free($1); $1 = NULL;
+                       free($3); $3 = NULL;
+               }
+       |
+       IDENTIFIER '.' IDENTIFIER IS NOT NULL_T
+               {
+                       sdb_store_matcher_t *m;
+                       m = sdb_store_matcher_parse_cmp($1, $3, "IS", NULL);
+                       free($1); $1 = NULL;
+                       free($3); $3 = NULL;
+
+                       /* sdb_store_inv_matcher return NULL if m==NULL */
+                       $$ = sdb_store_inv_matcher(m);
+                       sdb_object_deref(SDB_OBJ(m));
+               }
        ;
 
 op:
index 0396db53e0d742f17ca12de0fcec788da4c24045..a7b72b43794541985e722ca2f2c6ea3a61848e12 100644 (file)
@@ -108,12 +108,16 @@ float             ({float1}|{float2}|{float3}|{float4}|{float5})
                        return AND;
                else if (! strcasecmp(yytext, "FETCH"))
                        return FETCH;
+               else if (! strcasecmp(yytext, "IS"))
+                       return IS;
                else if (! strcasecmp(yytext, "LIST"))
                        return LIST;
                else if (! strcasecmp(yytext, "LOOKUP"))
                        return LOOKUP;
                else if (! strcasecmp(yytext, "NOT"))
                        return NOT;
+               else if (! strcasecmp(yytext, "NULL"))
+                       return NULL_T;
                else if (! strcasecmp(yytext, "OR"))
                        return OR;
                else if (! strcasecmp(yytext, "WHERE"))
index aa28c96d1be99c57806dd79965ccc38ccf3ae2ce..7a9dc44bbc887cd00583675ed841d8c9c70ec30f 100644 (file)
@@ -209,6 +209,13 @@ sdb_store_name_matcher(int type, const char *name, _Bool re);
 sdb_store_matcher_t *
 sdb_store_attr_matcher(const char *name, const char *value, _Bool re);
 
+/*
+ * sdb_store_isnull_matcher:
+ * Creates a matcher matching "missing" attributes.
+ */
+sdb_store_matcher_t *
+sdb_store_isnull_matcher(const char *attr_name);
+
 /*
  * sdb_store_lt_matcher, sdb_store_le_matcher, sdb_store_eq_matcher,
  * sdb_store_ge_matcher, sdb_store_gt_matcher:
index 8c626606c3c2d1900aa580a6f721a180fd66c382..4b9b6728d00ebb7bece003f7dc47de1f4ad3c236 100644 (file)
@@ -106,6 +106,22 @@ START_TEST(test_parse)
                  "attribute.foo = "
                  "-12e+3",              -1,  1, CONNECTION_LOOKUP },
 
+               /* NULL */
+               { "LOOKUP hosts WHERE "
+                 "attribute.foo "
+                 "IS NULL",             -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts WHERE "
+                 "attribute.foo "
+                 "IS NOT NULL",         -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts WHERE "
+                 "NOT attribute.foo "
+                 "IS NULL",             -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts WHERE "
+                 "host.name IS NULL",   -1, -1, 0 },
+               { "LOOKUP hosts WHERE "
+                 "service.name "
+                 "IS NULL",             -1, -1, 0 },
+
                /* invalid numeric constants */
                { "LOOKUP hosts WHERE "
                  "attribute.foo = "
@@ -210,6 +226,16 @@ START_TEST(test_parse_matcher)
                { "host.name =~ 'pattern' OR "
                  "service.name =~ 'pattern'",      -1,  MATCHER_OR },
                { "NOT host.name = 'host'",         -1,  MATCHER_NOT },
+               /* numeric expressions */
+               { "attribute.foo < 123",            -1,  MATCHER_LT },
+               { "attribute.foo <= 123",           -1,  MATCHER_LE },
+               { "attribute.foo = 123",            -1,  MATCHER_EQ },
+               { "attribute.foo >= 123",           -1,  MATCHER_GE },
+               { "attribute.foo > 123",            -1,  MATCHER_GT },
+               /* NULL; while this is an implementation detail,
+                * IS NULL currently maps to an equality matcher */
+               { "attribute.foo IS NULL",          -1,  MATCHER_ISNULL },
+               { "attribute.foo IS NOT NULL",      -1,  MATCHER_NOT },
 
                /* check operator precedence */
                { "host.name = 'name' OR "