Code

store: Add sdb_store_query_prepare_matcher().
[sysdb.git] / t / unit / frontend / parser_test.c
index 48a1a97856b0304e159d2c26bddbb5ad340f18c2..65ec6cad5b8797052b16d7c5a45bc1455018de67 100644 (file)
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#if HAVE_CONFIG_H
+#      include "config.h"
+#endif
+
 #include "frontend/connection.h"
 #include "frontend/parser.h"
 #include "core/store-private.h"
 #include "core/object.h"
-#include "libsysdb_test.h"
+#include "testutils.h"
 
 #include <check.h>
 #include <limits.h>
@@ -60,18 +64,61 @@ struct {
          "'host'.'metric'",     -1,  1, SDB_CONNECTION_FETCH  },
 
        /* LIST commands */
-       { "LIST hosts",          -1,  1, SDB_CONNECTION_LIST   },
-       { "LIST hosts -- foo",   -1,  1, SDB_CONNECTION_LIST   },
-       { "LIST hosts;",         -1,  1, SDB_CONNECTION_LIST   },
-       { "LIST hosts; INVALID", 11,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts",            -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts -- foo",     -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts;",           -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts; INVALID",   11,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts FILTER "
+         "age > 60s",             -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST services",         -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST services FILTER "
+         "age > 60s",             -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST metrics",          -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST metrics FILTER "
+         "age > 60s",             -1,  1, SDB_CONNECTION_LIST   },
+       /* field access */
+       { "LIST hosts FILTER "
+         "name = 'a'",            -1,  1, SDB_CONNECTION_LIST   },
        { "LIST hosts FILTER "
-         "age > 60s",           -1,  1, SDB_CONNECTION_LIST   },
-       { "LIST services",       -1,  1, SDB_CONNECTION_LIST   },
+         "last_update > 1s",      -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts FILTER "
+         "age > 120s",            -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts FILTER "
+         "interval > 10s",        -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts FILTER "
+         "backend = ['b']",       -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST hosts FILTER "
+         "value = 'a'",           -1, -1, 0 },
+       { "LIST hosts FILTER ANY "
+         "attribute.value = 'a'", -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST services FILTER "
+         "name = 'a'",            -1,  1, SDB_CONNECTION_LIST   },
        { "LIST services FILTER "
-         "age > 60s",           -1,  1, SDB_CONNECTION_LIST   },
-       { "LIST metrics",        -1,  1, SDB_CONNECTION_LIST   },
+         "last_update > 1s",      -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST services FILTER "
+         "age > 120s",            -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST services FILTER "
+         "interval > 10s",        -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST services FILTER "
+         "backend = ['b']",       -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST services FILTER "
+         "value = 'a'",           -1, -1, 0 },
+       { "LIST services FILTER ANY "
+         "attribute.value = 'a'", -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST metrics FILTER "
+         "name = 'a'",            -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST metrics FILTER "
+         "last_update > 1s",      -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST metrics FILTER "
+         "age > 120s",            -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST metrics FILTER "
+         "interval > 10s",        -1,  1, SDB_CONNECTION_LIST   },
+       { "LIST metrics FILTER "
+         "backend = ['b']",       -1,  1, SDB_CONNECTION_LIST   },
        { "LIST metrics FILTER "
-         "age > 60s",           -1,  1, SDB_CONNECTION_LIST   },
+         "value = 'a'",           -1, -1, 0 },
+       { "LIST metrics FILTER ANY "
+         "attribute.value = 'a'", -1,  1, SDB_CONNECTION_LIST   },
 
        /* LOOKUP commands */
        { "LOOKUP hosts",        -1,  1, SDB_CONNECTION_LOOKUP },
@@ -81,48 +128,48 @@ struct {
          "name = 'host'",       -1,  1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
          "name =~ 'p' AND "
-         "ANY service =~ 'p'",  -1,  1, SDB_CONNECTION_LOOKUP },
+         "ANY service.name =~ 'p'", -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING NOT "
          "name =~ 'p' AND "
-         "ANY service =~ 'p'",  -1,  1, SDB_CONNECTION_LOOKUP },
+         "ANY service.name =~ 'p'", -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
          "name =~ 'p' AND "
-         "ANY service =~ 'p' OR "
-         "ANY service =~ 'r'",  -1,  1, SDB_CONNECTION_LOOKUP },
+         "ANY service.name =~ 'p' OR "
+         "ANY service.name =~ 'r'", -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING NOT "
          "name =~ 'p' AND "
-         "ANY service =~ 'p' OR "
-         "ANY service =~ 'r'",  -1,  1, SDB_CONNECTION_LOOKUP },
+         "ANY service.name =~ 'p' OR "
+         "ANY service.name =~ 'r'", -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
          "name =~ 'p' "
-         "FILTER age > 1D",    -1,   1, SDB_CONNECTION_LOOKUP },
+         "FILTER age > 1D",         -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
          "name =~ 'p' "
          "FILTER age > 1D AND "
-         "interval < 240s" ,   -1,   1, SDB_CONNECTION_LOOKUP },
+         "interval < 240s" ,        -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
          "name =~ 'p' "
-         "FILTER NOT age>1D",  -1,   1, SDB_CONNECTION_LOOKUP },
+         "FILTER NOT age>1D",       -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
          "name =~ 'p' "
          "FILTER age>"
-         "interval",           -1,   1, SDB_CONNECTION_LOOKUP },
+         "interval",                -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
-         "host.name =~ 'p'",   -1,   1, SDB_CONNECTION_LOOKUP },
-       { "LOOKUP services",    -1,   1, SDB_CONNECTION_LOOKUP },
+         "host.name =~ 'p'",        -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP services",         -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP services MATCHING ANY "
-         "attribute =~ 'a'",   -1,   1, SDB_CONNECTION_LOOKUP },
+         "attribute.name =~ 'a'",   -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP services MATCHING "
-         "host.name = 'p'",    -1,   1, SDB_CONNECTION_LOOKUP },
+         "host.name = 'p'",         -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP services MATCHING "
-         "service.name = 'p'", -1,   1, SDB_CONNECTION_LOOKUP },
-       { "LOOKUP metrics",     -1,   1, SDB_CONNECTION_LOOKUP },
+         "service.name = 'p'",      -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP metrics",          -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP metrics MATCHING ANY "
-         "attribute =~ 'a'",   -1,   1, SDB_CONNECTION_LOOKUP },
+         "attribute.name =~ 'a'",   -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP metrics MATCHING "
-         "host.name = 'p'",    -1,   1, SDB_CONNECTION_LOOKUP },
+         "host.name = 'p'",         -1,   1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP metrics MATCHING "
-         "metric.name = 'p'",  -1,   1, SDB_CONNECTION_LOOKUP },
+         "metric.name = 'p'",       -1,   1, SDB_CONNECTION_LOOKUP },
 
        /* TIMESERIES commands */
        { "TIMESERIES 'host'.'metric' "
@@ -248,6 +295,67 @@ struct {
        { "LOOKUP hosts MATCHING "
          "backend = ['a','b']", -1,  1, SDB_CONNECTION_LOOKUP },
 
+       /* array iteration */
+       { "LOOKUP hosts MATCHING "
+         "'foo' IN backend",   -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING 'foo' "
+         "NOT IN backend",   -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "['foo','bar'] "
+         "IN backend ",        -1,   1, SDB_CONNECTION_LOOKUP },
+       /* attribute type is unknown */
+       { "LOOKUP hosts MATCHING "
+         "attribute['backend'] "
+         "IN backend ",        -1,   1, SDB_CONNECTION_LOOKUP },
+       /* type mismatch */
+       { "LOOKUP hosts MATCHING "
+         "1 IN backend ",      -1,  -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "1 NOT IN backend ",  -1,  -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend < 'b'",  -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend <= 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend = 'b'",  -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend != 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend >= 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend > 'b'",  -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend =~ 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ANY backend !~ 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       /* right operand is an array */
+       { "LOOKUP hosts MATCHING "
+         "ANY backend !~ backend",
+                               -1,  -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend < 'b'",  -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend <= 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend = 'b'",  -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend != 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend >= 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend > 'b'",  -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend =~ 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       { "LOOKUP hosts MATCHING "
+         "ALL backend !~ 'b'", -1,   1, SDB_CONNECTION_LOOKUP },
+       /* attribute type is unknown */
+       { "LOOKUP hosts MATCHING "
+         "ANY backend = attribute['backend']",
+                               -1,   1, SDB_CONNECTION_LOOKUP },
+       /* type mismatch */
+       { "LOOKUP hosts MATCHING "
+         "ANY backend = 1",    -1,  -1, 0 },
+
        /* valid operand types */
        { "LOOKUP hosts MATCHING "
          "age * 1 > 0s",        -1,  1, SDB_CONNECTION_LOOKUP },
@@ -277,7 +385,7 @@ struct {
          "NOT attribute['foo'] "
          "IS NULL",             -1,  1, SDB_CONNECTION_LOOKUP },
        { "LOOKUP hosts MATCHING "
-         "ANY service IS NULL", -1, -1, 0 },
+         "ANY service.name IS NULL", -1, -1, 0 },
 
        /* invalid numeric constants */
        { "LOOKUP hosts MATCHING "
@@ -309,6 +417,8 @@ struct {
        /* type mismatches */
        { "LOOKUP hosts MATCHING "
          "age > 0",             -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "NOT age > 0",         -1, -1, 0 },
        { "LOOKUP hosts MATCHING "
          "age >= 0",            -1, -1, 0 },
        { "LOOKUP hosts MATCHING "
@@ -336,6 +446,58 @@ struct {
          "name / 1 = ''",       -1, -1, 0 },
        { "LOOKUP hosts MATCHING "
          "name % 1 = ''",       -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "(name % 1) + 1 = ''", -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "1 + (name % 1) = ''", -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "'' = 1 + (name % 1)", -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "age > 0 AND "
+         "age = 0s",            -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "age = 0s AND "
+         "age > 0",             -1, -1, 0 },
+       { "LOOKUP services MATCHING "
+         "host.name > 0",       -1, -1, 0 },
+       { "LOOKUP services MATCHING "
+         "backend > 'b'",       -1, -1, 0 },
+       { "LOOKUP services MATCHING "
+         "'b' > backend",       -1, -1, 0 },
+       { "LOOKUP services MATCHING "
+         "attribute['a'] > backend",
+                                -1, -1, 0 },
+       { "LOOKUP services MATCHING "
+         "backend > attribute['a']",
+                                -1, -1, 0 },
+       { "LOOKUP services MATCHING "
+         "host.name + 1 = ''",  -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "'a' + 1 IN 'b'",      -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "'a' IN 'b' - 1",      -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "name + 1 IN 'b'",     -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "'a' IN name - 1",     -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "'b' IN 'abc'",        -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "1 IN age",            -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "name =~ 'a' + 1",     -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "name =~ name + 1",    -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "name + 1 =~ 'a'",     -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "name =~ 1",           -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "name + 1 IS NULL",    -1, -1, 0 },
+       { "LOOKUP hosts FILTER "
+         "name + 1 IS NULL",    -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "ANY 'patt' =~ 'p'",  -1,  -1, 0 },
 
        /* comments */
        { "/* some comment */",  -1,  0, 0 },
@@ -383,25 +545,27 @@ struct {
          "attribute['foo'] <= "
          "'f' || oo",           -1, -1, 0 },
        { "LOOKUP hosts MATCHING "
-         "ANY host = 'host'",   -1, -1, 0 },
+         "ANY host.name = 'host'",   -1, -1, 0 },
        { "LOOKUP hosts MATCHING "
-         "service.name = 's'",  -1, -1, 0 },
+         "ANY service.name > 1",     -1, -1, 0 },
+       { "LOOKUP hosts MATCHING "
+         "service.name = 's'",       -1, -1, 0 },
        { "LOOKUP services MATCHING "
-         "ANY host = 'host'",   -1, -1, 0 },
+         "ANY host.name = 'host'",   -1, -1, 0 },
        { "LOOKUP services MATCHING "
-         "ANY service = 'svc'", -1, -1, 0 },
+         "ANY service.name = 'svc'", -1, -1, 0 },
        { "LOOKUP services MATCHING "
-         "ANY metric = 'm'",    -1, -1, 0 },
+         "ANY metric.name = 'm'",    -1, -1, 0 },
        { "LOOKUP services MATCHING "
-         "metric.name = 'm'",   -1, -1, 0 },
+         "metric.name = 'm'",        -1, -1, 0 },
        { "LOOKUP metrics MATCHING "
-         "ANY host = 'host'",   -1, -1, 0 },
+         "ANY host.name = 'host'",   -1, -1, 0 },
        { "LOOKUP metrics MATCHING "
-         "ANY service = 'svc'", -1, -1, 0 },
+         "ANY service.name = 'svc'", -1, -1, 0 },
        { "LOOKUP metrics MATCHING "
-         "ANY metric = 'm'",    -1, -1, 0 },
+         "ANY metric.name = 'm'",    -1, -1, 0 },
        { "LOOKUP metrics MATCHING "
-         "service.name = 'm'",  -1, -1, 0 },
+         "service.name = 'm'",       -1, -1, 0 },
 
        /* invalid STORE commands */
        { "STORE host "
@@ -507,162 +671,168 @@ struct {
        { "ALL backend > 'be'",           -1,  MATCHER_ALL },
        { "ANY backend &^ 'be'",          -1,  -1 },
        /* match hosts by service */
-       { "ANY service < 'name'",         -1,  MATCHER_ANY },
-       { "ANY service <= 'name'",        -1,  MATCHER_ANY },
-       { "ANY service = 'name'",         -1,  MATCHER_ANY },
-       { "ANY service != 'name'",        -1,  MATCHER_ANY },
-       { "ANY service >= 'name'",        -1,  MATCHER_ANY },
-       { "ANY service > 'name'",         -1,  MATCHER_ANY },
-       { "ANY service =~ 'pattern'",     -1,  MATCHER_ANY },
-       { "ANY service !~ 'pattern'",     -1,  MATCHER_ANY },
-       { "ANY service &^ 'name'",        -1,  -1 },
-       { "ALL service < 'name'",         -1,  MATCHER_ALL },
-       { "ALL service <= 'name'",        -1,  MATCHER_ALL },
-       { "ALL service = 'name'",         -1,  MATCHER_ALL },
-       { "ALL service != 'name'",        -1,  MATCHER_ALL },
-       { "ALL service >= 'name'",        -1,  MATCHER_ALL },
-       { "ALL service > 'name'",         -1,  MATCHER_ALL },
-       { "ALL service =~ 'pattern'",     -1,  MATCHER_ALL },
-       { "ALL service !~ 'pattern'",     -1,  MATCHER_ALL },
-       { "ALL service &^ 'name'",        -1,  -1 },
+       { "ANY service.name < 'name'",         -1,  MATCHER_ANY },
+       { "ANY service.name <= 'name'",        -1,  MATCHER_ANY },
+       { "ANY service.name = 'name'",         -1,  MATCHER_ANY },
+       { "ANY service.name != 'name'",        -1,  MATCHER_ANY },
+       { "ANY service.name >= 'name'",        -1,  MATCHER_ANY },
+       { "ANY service.name > 'name'",         -1,  MATCHER_ANY },
+       { "ANY service.name =~ 'pattern'",     -1,  MATCHER_ANY },
+       { "ANY service.name !~ 'pattern'",     -1,  MATCHER_ANY },
+       { "ANY service.name &^ 'name'",        -1,  -1 },
+       { "ALL service.name < 'name'",         -1,  MATCHER_ALL },
+       { "ALL service.name <= 'name'",        -1,  MATCHER_ALL },
+       { "ALL service.name = 'name'",         -1,  MATCHER_ALL },
+       { "ALL service.name != 'name'",        -1,  MATCHER_ALL },
+       { "ALL service.name >= 'name'",        -1,  MATCHER_ALL },
+       { "ALL service.name > 'name'",         -1,  MATCHER_ALL },
+       { "ALL service.name =~ 'pattern'",     -1,  MATCHER_ALL },
+       { "ALL service.name !~ 'pattern'",     -1,  MATCHER_ALL },
+       { "ALL service.name &^ 'name'",        -1,  -1 },
+       { "ANY service < 'name'",              -1,  -1 },
        /* match hosts by metric */
-       { "ANY metric < 'name'",          -1,  MATCHER_ANY },
-       { "ANY metric <= 'name'",         -1,  MATCHER_ANY },
-       { "ANY metric = 'name'",          -1,  MATCHER_ANY },
-       { "ANY metric != 'name'",         -1,  MATCHER_ANY },
-       { "ANY metric >= 'name'",         -1,  MATCHER_ANY },
-       { "ANY metric > 'name'",          -1,  MATCHER_ANY },
-       { "ANY metric =~ 'pattern'",      -1,  MATCHER_ANY },
-       { "ANY metric !~ 'pattern'",      -1,  MATCHER_ANY },
-       { "ANY metric &^ 'pattern'",      -1,  -1 },
-       { "ALL metric < 'name'",          -1,  MATCHER_ALL },
-       { "ALL metric <= 'name'",         -1,  MATCHER_ALL },
-       { "ALL metric = 'name'",          -1,  MATCHER_ALL },
-       { "ALL metric != 'name'",         -1,  MATCHER_ALL },
-       { "ALL metric >= 'name'",         -1,  MATCHER_ALL },
-       { "ALL metric > 'name'",          -1,  MATCHER_ALL },
-       { "ALL metric =~ 'pattern'",      -1,  MATCHER_ALL },
-       { "ALL metric !~ 'pattern'",      -1,  MATCHER_ALL },
-       { "ALL metric &^ 'pattern'",      -1,  -1 },
+       { "ANY metric.name < 'name'",          -1,  MATCHER_ANY },
+       { "ANY metric.name <= 'name'",         -1,  MATCHER_ANY },
+       { "ANY metric.name = 'name'",          -1,  MATCHER_ANY },
+       { "ANY metric.name != 'name'",         -1,  MATCHER_ANY },
+       { "ANY metric.name >= 'name'",         -1,  MATCHER_ANY },
+       { "ANY metric.name > 'name'",          -1,  MATCHER_ANY },
+       { "ANY metric.name =~ 'pattern'",      -1,  MATCHER_ANY },
+       { "ANY metric.name !~ 'pattern'",      -1,  MATCHER_ANY },
+       { "ANY metric.name &^ 'pattern'",      -1,  -1 },
+       { "ALL metric.name < 'name'",          -1,  MATCHER_ALL },
+       { "ALL metric.name <= 'name'",         -1,  MATCHER_ALL },
+       { "ALL metric.name = 'name'",          -1,  MATCHER_ALL },
+       { "ALL metric.name != 'name'",         -1,  MATCHER_ALL },
+       { "ALL metric.name >= 'name'",         -1,  MATCHER_ALL },
+       { "ALL metric.name > 'name'",          -1,  MATCHER_ALL },
+       { "ALL metric.name =~ 'pattern'",      -1,  MATCHER_ALL },
+       { "ALL metric.name !~ 'pattern'",      -1,  MATCHER_ALL },
+       { "ALL metric.name &^ 'pattern'",      -1,  -1 },
+       { "ANY metric <= 'name'",              -1,  -1 },
        /* match hosts by attribute */
-       { "ANY attribute < 'name'",       -1,  MATCHER_ANY },
-       { "ANY attribute <= 'name'",      -1,  MATCHER_ANY },
-       { "ANY attribute = 'name'",       -1,  MATCHER_ANY },
-       { "ANY attribute != 'name'",      -1,  MATCHER_ANY },
-       { "ANY attribute >= 'name'",      -1,  MATCHER_ANY },
-       { "ANY attribute > 'name'",       -1,  MATCHER_ANY },
-       { "ANY attribute =~ 'pattern'",   -1,  MATCHER_ANY },
-       { "ANY attribute !~ 'pattern'",   -1,  MATCHER_ANY },
-       { "ANY attribute &^ 'pattern'",   -1,  -1 },
-       { "ALL attribute < 'name'",       -1,  MATCHER_ALL },
-       { "ALL attribute <= 'name'",      -1,  MATCHER_ALL },
-       { "ALL attribute = 'name'",       -1,  MATCHER_ALL },
-       { "ALL attribute != 'name'",      -1,  MATCHER_ALL },
-       { "ALL attribute >= 'name'",      -1,  MATCHER_ALL },
-       { "ALL attribute > 'name'",       -1,  MATCHER_ALL },
-       { "ALL attribute =~ 'pattern'",   -1,  MATCHER_ALL },
-       { "ALL attribute !~ 'pattern'",   -1,  MATCHER_ALL },
-       { "ALL attribute &^ 'pattern'",   -1,  -1 },
+       { "ANY attribute.name < 'name'",       -1,  MATCHER_ANY },
+       { "ANY attribute.name <= 'name'",      -1,  MATCHER_ANY },
+       { "ANY attribute.name = 'name'",       -1,  MATCHER_ANY },
+       { "ANY attribute.name != 'name'",      -1,  MATCHER_ANY },
+       { "ANY attribute.name >= 'name'",      -1,  MATCHER_ANY },
+       { "ANY attribute.name > 'name'",       -1,  MATCHER_ANY },
+       { "ANY attribute.name =~ 'pattern'",   -1,  MATCHER_ANY },
+       { "ANY attribute.name !~ 'pattern'",   -1,  MATCHER_ANY },
+       { "ANY attribute.name &^ 'pattern'",   -1,  -1 },
+       { "ALL attribute.name < 'name'",       -1,  MATCHER_ALL },
+       { "ALL attribute.name <= 'name'",      -1,  MATCHER_ALL },
+       { "ALL attribute.name = 'name'",       -1,  MATCHER_ALL },
+       { "ALL attribute.name != 'name'",      -1,  MATCHER_ALL },
+       { "ALL attribute.name >= 'name'",      -1,  MATCHER_ALL },
+       { "ALL attribute.name > 'name'",       -1,  MATCHER_ALL },
+       { "ALL attribute.name =~ 'pattern'",   -1,  MATCHER_ALL },
+       { "ALL attribute.name !~ 'pattern'",   -1,  MATCHER_ALL },
+       { "ALL attribute.name &^ 'pattern'",   -1,  -1 },
+       { "ANY attribute !~ 'pattern'",        -1,  -1 },
        /* composite expressions */
        { "name =~ 'pattern' AND "
-         "ANY service =~ 'pattern'",     -1,  MATCHER_AND },
+         "ANY service.name =~ 'pattern'",     -1,  MATCHER_AND },
        { "name =~ 'pattern' OR "
-         "ANY service =~ 'pattern'",     -1,  MATCHER_OR },
-       { "NOT name = 'host'",            -1,  MATCHER_NOT },
+         "ANY service.name =~ 'pattern'",     -1,  MATCHER_OR },
+       { "NOT 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 },
+       { "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 },
        /* datetime expressions */
        { "attribute['foo'] = "
-         "2014-08-16",                   -1,  MATCHER_EQ },
+         "2014-08-16",                     -1,  MATCHER_EQ },
        { "attribute['foo'] = "
-         "17:23",                        -1,  MATCHER_EQ },
+         "17:23",                          -1,  MATCHER_EQ },
        { "attribute['foo'] = "
-         "17:23:53",                     -1,  MATCHER_EQ },
+         "17:23:53",                       -1,  MATCHER_EQ },
        { "attribute['foo'] = "
-         "17:23:53.123",                 -1,  MATCHER_EQ },
+         "17:23:53.123",                   -1,  MATCHER_EQ },
        { "attribute['foo'] = "
-         "17:23:53.123456789",           -1,  MATCHER_EQ },
+         "17:23:53.123456789",             -1,  MATCHER_EQ },
        { "attribute['foo'] = "
-         "2014-08-16 17:23",             -1,  MATCHER_EQ },
+         "2014-08-16 17:23",               -1,  MATCHER_EQ },
        { "attribute['foo'] = "
-         "2014-08-16 17:23:53",          -1,  MATCHER_EQ },
+         "2014-08-16 17:23:53",            -1,  MATCHER_EQ },
        /* 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_ISNNULL },
+       { "attribute['foo'] IS NULL",       -1,  MATCHER_ISNULL },
+       { "attribute['foo'] IS NOT NULL",   -1,  MATCHER_ISNNULL },
        /* array expressions */
-       { "backend < ['a']",              -1,  MATCHER_LT },
-       { "backend <= ['a']",             -1,  MATCHER_LE },
-       { "backend = ['a']",              -1,  MATCHER_EQ },
-       { "backend != ['a']",             -1,  MATCHER_NE },
-       { "backend >= ['a']",             -1,  MATCHER_GE },
-       { "backend > ['a']",              -1,  MATCHER_GT },
-       { "backend &^ ['a']",             -1,  -1 },
+       { "backend < ['a']",                -1,  MATCHER_LT },
+       { "backend <= ['a']",               -1,  MATCHER_LE },
+       { "backend = ['a']",                -1,  MATCHER_EQ },
+       { "backend != ['a']",               -1,  MATCHER_NE },
+       { "backend >= ['a']",               -1,  MATCHER_GE },
+       { "backend > ['a']",                -1,  MATCHER_GT },
+       { "backend &^ ['a']",               -1,  -1 },
 
        /* object field matchers */
-       { "name < 'a'",                   -1,  MATCHER_LT },
-       { "name <= 'a'",                  -1,  MATCHER_LE },
-       { "name = 'a'",                   -1,  MATCHER_EQ },
-       { "name != 'a'",                  -1,  MATCHER_NE },
-       { "name >= 'a'",                  -1,  MATCHER_GE },
-       { "name > 'a'",                   -1,  MATCHER_GT },
-       { "last_update < 2014-10-01",     -1,  MATCHER_LT },
-       { "last_update <= 2014-10-01",    -1,  MATCHER_LE },
-       { "last_update = 2014-10-01",     -1,  MATCHER_EQ },
-       { "last_update != 2014-10-01",    -1,  MATCHER_NE },
-       { "last_update >= 2014-10-01",    -1,  MATCHER_GE },
-       { "last_update > 2014-10-01",     -1,  MATCHER_GT },
-       { "Last_Update >= 24D",           -1,  MATCHER_GE },
-       { "age < 20s",                    -1,  MATCHER_LT },
-       { "age <= 20s",                   -1,  MATCHER_LE },
-       { "age = 20s",                    -1,  MATCHER_EQ },
-       { "age != 20s",                   -1,  MATCHER_NE },
-       { "age >= 20s",                   -1,  MATCHER_GE },
-       { "age > 20s",                    -1,  MATCHER_GT },
-       { "AGE <= 1m",                    -1,  MATCHER_LE },
-       { "age > 1M",                     -1,  MATCHER_GT },
-       { "age != 20Y",                   -1,  MATCHER_NE },
-       { "age <= 2 * interval",          -1,  MATCHER_LE },
-       { "interval < 20s",               -1,  MATCHER_LT },
-       { "interval <= 20s",              -1,  MATCHER_LE },
-       { "interval = 20s",               -1,  MATCHER_EQ },
-       { "interval != 20s",              -1,  MATCHER_NE },
-       { "interval >= 20s",              -1,  MATCHER_GE },
-       { "interval > 20s",               -1,  MATCHER_GT },
-       { "'be' IN backend",              -1,  MATCHER_IN },
+       { "name < 'a'",                     -1,  MATCHER_LT },
+       { "name <= 'a'",                    -1,  MATCHER_LE },
+       { "name = 'a'",                     -1,  MATCHER_EQ },
+       { "name != 'a'",                    -1,  MATCHER_NE },
+       { "name >= 'a'",                    -1,  MATCHER_GE },
+       { "name > 'a'",                     -1,  MATCHER_GT },
+       { "last_update < 2014-10-01",       -1,  MATCHER_LT },
+       { "last_update <= 2014-10-01",      -1,  MATCHER_LE },
+       { "last_update = 2014-10-01",       -1,  MATCHER_EQ },
+       { "last_update != 2014-10-01",      -1,  MATCHER_NE },
+       { "last_update >= 2014-10-01",      -1,  MATCHER_GE },
+       { "last_update > 2014-10-01",       -1,  MATCHER_GT },
+       { "Last_Update >= 24D",             -1,  MATCHER_GE },
+       { "age < 20s",                      -1,  MATCHER_LT },
+       { "age <= 20s",                     -1,  MATCHER_LE },
+       { "age = 20s",                      -1,  MATCHER_EQ },
+       { "age != 20s",                     -1,  MATCHER_NE },
+       { "age >= 20s",                     -1,  MATCHER_GE },
+       { "age > 20s",                      -1,  MATCHER_GT },
+       { "AGE <= 1m",                      -1,  MATCHER_LE },
+       { "age > 1M",                       -1,  MATCHER_GT },
+       { "age != 20Y",                     -1,  MATCHER_NE },
+       { "age <= 2 * interval",            -1,  MATCHER_LE },
+       { "interval < 20s",                 -1,  MATCHER_LT },
+       { "interval <= 20s",                -1,  MATCHER_LE },
+       { "interval = 20s",                 -1,  MATCHER_EQ },
+       { "interval != 20s",                -1,  MATCHER_NE },
+       { "interval >= 20s",                -1,  MATCHER_GE },
+       { "interval > 20s",                 -1,  MATCHER_GT },
+       { "'be' IN backend",                -1,  MATCHER_IN },
+       { "'be' NOT IN backend",            -1,  MATCHER_NIN },
+       { "['a','b'] IN backend",           -1,  MATCHER_IN },
+       { "['a','b'] NOT IN backend",       -1,  MATCHER_NIN },
 
        /* check operator precedence */
        { "name = 'name' OR "
-         "ANY service = 'name' AND "
-         "ANY attribute = 'name' OR "
-         "attribute['foo'] = 'bar'",     -1,  MATCHER_OR },
+         "ANY service.name = 'name' AND "
+         "ANY attribute.name = 'name' OR "
+         "attribute['foo'] = 'bar'",       -1,  MATCHER_OR },
        { "name = 'name' AND "
-         "ANY service = 'name' AND "
-         "ANY attribute = 'name' OR "
-         "attribute['foo'] = 'bar'",     -1,  MATCHER_OR },
+         "ANY service.name = 'name' AND "
+         "ANY attribute.name = 'name' OR "
+         "attribute['foo'] = 'bar'",       -1,  MATCHER_OR },
        { "name = 'name' AND "
-         "ANY service = 'name' OR "
-         "ANY attribute = 'name' AND "
-         "attribute['foo'] = 'bar'",     -1,  MATCHER_OR },
+         "ANY service.name = 'name' OR "
+         "ANY attribute.name = 'name' AND "
+         "attribute['foo'] = 'bar'",       -1,  MATCHER_OR },
        { "(name = 'name' OR "
-         "ANY service = 'name') AND "
-         "(ANY attribute = 'name' OR "
-         "attribute['foo'] = 'bar')",    -1,  MATCHER_AND },
+         "ANY service.name = 'name') AND "
+         "(ANY attribute.name = 'name' OR "
+         "attribute['foo'] = 'bar')",      -1,  MATCHER_AND },
        { "NOT name = 'name' OR "
-         "ANY service = 'name'",         -1,  MATCHER_OR },
+         "ANY service.name = 'name'",      -1,  MATCHER_OR },
        { "NOT name = 'name' OR "
-         "NOT ANY service = 'name'",     -1,  MATCHER_OR },
+         "NOT ANY service.name = 'name'",  -1,  MATCHER_OR },
        { "NOT (name = 'name' OR "
-         "NOT ANY service = 'name')",    -1,  MATCHER_NOT },
+         "NOT ANY service.name = 'name')", -1,  MATCHER_NOT },
 
        /* syntax errors */
-       { "LIST",                         -1, -1 },
-       { "foo &^ bar",                   -1, -1 },
-       { "invalid",                      -1, -1 },
+       { "LIST",                           -1, -1 },
+       { "foo &^ bar",                     -1, -1 },
+       { "invalid",                        -1, -1 },
 };
 
 START_TEST(test_parse_matcher)
@@ -787,20 +957,15 @@ START_TEST(test_parse_expr)
 }
 END_TEST
 
-Suite *
-fe_parser_suite(void)
+TEST_MAIN("frontend::parser")
 {
-       Suite *s = suite_create("frontend::parser");
-       TCase *tc;
-
-       tc = tcase_create("core");
+       TCase *tc = tcase_create("core");
        TC_ADD_LOOP_TEST(tc, parse);
        TC_ADD_LOOP_TEST(tc, parse_matcher);
        TC_ADD_LOOP_TEST(tc, parse_expr);
-       suite_add_tcase(s, tc);
-
-       return s;
-} /* util_parser_suite */
+       ADD_TCASE(tc);
+}
+TEST_MAIN_END
 
 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */