Code

frontend/parser: Added support for string concatenation.
[sysdb.git] / t / unit / frontend / parser_test.c
index e245c29e56f1b89d279c5c7052c63208dae7b2a2..0c7b30448c41d2231060355abc4381b4b3ec8026 100644 (file)
@@ -52,30 +52,166 @@ START_TEST(test_parse)
                { ";;",                  -1,  0, 0 },
 
                /* valid commands */
-               { "FETCH 'host'",        -1,  1, CONNECTION_FETCH  },
-               { "LIST",                -1,  1, CONNECTION_LIST   },
-               { "LIST -- comment",     -1,  1, CONNECTION_LIST   },
-               { "LIST;",               -1,  1, CONNECTION_LIST   },
-               { "LIST; INVALID",        5,  1, CONNECTION_LIST   },
-
-               { "LOOKUP hosts WHERE "
-                 "host.name = 'host'",  -1,  1, CONNECTION_LOOKUP },
-               { "LOOKUP hosts WHERE NOT "
-                 "host.name = 'host'",  -1,  1, CONNECTION_LOOKUP },
-               { "LOOKUP hosts WHERE "
-                 "host.name =~ 'p' AND "
-                 "service.name =~ 'p'", -1,  1, CONNECTION_LOOKUP },
-               { "LOOKUP hosts WHERE NOT "
-                 "host.name =~ 'p' AND "
-                 "service.name =~ 'p'", -1,  1, CONNECTION_LOOKUP },
-               { "LOOKUP hosts WHERE "
-                 "host.name =~ 'p' AND "
-                 "service.name =~ 'p' OR "
-                 "service.name =~ 'r'", -1,  1, CONNECTION_LOOKUP },
-               { "LOOKUP hosts WHERE NOT "
-                 "host.name =~ 'p' AND "
-                 "service.name =~ 'p' OR "
-                 "service.name =~ 'r'", -1,  1, CONNECTION_LOOKUP },
+               { "FETCH host 'host'",   -1,  1, CONNECTION_FETCH  },
+               { "FETCH host 'host' FILTER "
+                 "host = 'host'",       -1,  1, CONNECTION_FETCH  },
+
+               { "LIST hosts",          -1,  1, CONNECTION_LIST   },
+               { "LIST hosts -- foo",   -1,  1, CONNECTION_LIST   },
+               { "LIST hosts;",         -1,  1, CONNECTION_LIST   },
+               { "LIST hosts; INVALID", 11,  1, CONNECTION_LIST   },
+               { "LIST hosts FILTER "
+                 "host = 'host'",       -1,  1, CONNECTION_LIST   },
+               { "LIST services",       -1,  1, CONNECTION_LIST   },
+               { "LIST services FILTER "
+                 "service = 'svc'",     -1,  1, CONNECTION_LIST   },
+               { "LIST metrics",        -1,  1, CONNECTION_LIST   },
+               { "LIST metrics FILTER "
+                 "metric = 'metric'",   -1,  1, CONNECTION_LIST   },
+
+               { "LOOKUP hosts",        -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host = 'host'",       -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING NOT "
+                 "host = 'host'",       -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host =~ 'p' AND "
+                 "service =~ 'p'",      -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING NOT "
+                 "host =~ 'p' AND "
+                 "service =~ 'p'",      -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host =~ 'p' AND "
+                 "service =~ 'p' OR "
+                 "service =~ 'r'",      -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING NOT "
+                 "host =~ 'p' AND "
+                 "service =~ 'p' OR "
+                 "service =~ 'r'",      -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host =~ 'p' "
+                 "FILTER .age > 1D",    -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host =~ 'p' "
+                 "FILTER .age > 1D AND "
+                 ".interval < 240s" ,   -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host =~ 'p' "
+                 "FILTER NOT .age>1D",  -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host =~ 'p' "
+                 "FILTER .age>"
+                 ".interval",           -1,  1, CONNECTION_LOOKUP },
+
+               { "TIMESERIES 'host'.'metric' "
+                 "START 2014-01-01 "
+                 "END 2014-12-31 "
+                 "23:59:59",            -1,  1, CONNECTION_TIMESERIES },
+               { "TIMESERIES 'host'.'metric' "
+                 "START 2014-02-02 "
+                 "14:02",               -1,  1, CONNECTION_TIMESERIES },
+               { "TIMESERIES 'host'.'metric' "
+                 "END 2014-02-02",      -1,  1, CONNECTION_TIMESERIES },
+               { "TIMESERIES "
+                 "'host'.'metric'",     -1,  1, CONNECTION_TIMESERIES },
+
+               /* string constants */
+               { "LOOKUP hosts MATCHING "
+                 "host = ''''",         -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host = '''foo'",      -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host = 'f''oo'",      -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host = 'foo'''",      -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host = '''",          -1, -1, 0 },
+
+               /* numeric constants */
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "1234",                -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] != "
+                 "+234",                -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] < "
+                 "-234",                -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] > "
+                 "12.4",                -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] <= "
+                 "12. + .3",            -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] <= "
+                 "'f' || 'oo'",         -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] >= "
+                 ".4",                  -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "+12e3",               -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "+12e-3",              -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "-12e+3",              -1,  1, CONNECTION_LOOKUP },
+
+               /* date, time, interval constants */
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "1 Y 42D",             -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "1s 42D",              -1,  1, CONNECTION_LOOKUP },
+               /*
+                * TODO: Something like 1Y42D should work as well but it doesn't since
+                * the scanner will tokenize it into {digit}{identifier} :-/
+                *
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "1Y42D",               -1,  1, CONNECTION_LOOKUP },
+                */
+
+               /* NULL */
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] "
+                 "IS NULL",             -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] "
+                 "IS NOT NULL",         -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "NOT attribute[foo] "
+                 "IS NULL",             -1,  1, CONNECTION_LOOKUP },
+               { "LOOKUP hosts MATCHING "
+                 "host IS NULL",        -1, -1, 0 },
+               { "LOOKUP hosts MATCHING "
+                 "service IS NULL",     -1, -1, 0 },
+
+               /* invalid numeric constants */
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "+-12e+3",             -1, -1, 0 },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "-12e-+3",             -1, -1, 0 },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "e+3",                 -1, -1, 0 },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "3e",                  -1, -1, 0 },
+               /* following SQL standard, we don't support hex numbers */
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "0x12",                -1, -1, 0 },
+
+               /* invalid expressions */
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] = "
+                 "1.23 + 'foo'",        -1, -1, 0 },
 
                /* comments */
                { "/* some comment */",  -1,  0, 0 },
@@ -84,12 +220,36 @@ START_TEST(test_parse)
                /* syntax errors */
                { "INVALID",             -1, -1, 0 },
                { "FETCH host",          -1, -1, 0 },
-               { "LIST; INVALID",        8, -1, 0 },
+               { "FETCH 'host'",        -1, -1, 0 },
+               { "LIST hosts; INVALID", -1, -1, 0 },
                { "/* some incomplete",  -1, -1, 0 },
 
-               { "LOOKUP hosts",        -1, -1, 0 },
-               { "LOOKUP foo WHERE "
-                 "host.name = 'host'",  -1, -1, 0 },
+               { "LIST",                -1, -1, 0 },
+               { "LIST foo",            -1, -1, 0 },
+               { "LIST hosts MATCHING "
+                 "host = 'host'",       -1, -1, 0 },
+               { "LIST foo FILTER "
+                 "host = 'host'",       -1, -1, 0 },
+               { "FETCH host 'host' MATCHING "
+                 "host = 'host'",       -1, -1, 0 },
+               { "FETCH foo 'host'",    -1, -1, 0 },
+               { "FETCH foo 'host' FILTER "
+                 "host = 'host'",       -1, -1, 0 },
+
+               { "LOOKUP foo",          -1, -1, 0 },
+               { "LOOKUP foo MATCHING "
+                 "host = 'host'",       -1, -1, 0 },
+               { "LOOKUP foo FILTER "
+                 "host = 'host'",       -1, -1, 0 },
+               { "LOOKUP foo MATCHING "
+                 "host = 'host' FILTER "
+                 "host = 'host'",       -1, -1, 0 },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] <= "
+                 "f || 'oo'",           -1, -1, 0 },
+               { "LOOKUP hosts MATCHING "
+                 "attribute[foo] <= "
+                 "'f' || oo",           -1, -1, 0 },
        };
 
        size_t i;
@@ -137,60 +297,95 @@ START_TEST(test_parse_matcher)
                int expected;
        } golden_data[] = {
                /* empty expressions */
-               { NULL,                             -1, -1 },
-               { "",                               -1, -1 },
+               { NULL,                         -1, -1 },
+               { "",                           -1, -1 },
 
                /* valid expressions */
-               { "host.name = 'localhost'",        -1,  MATCHER_NAME },
-               { "host.name != 'localhost'",       -1,  MATCHER_NOT },
-               { "host.name =~ 'host'",            -1,  MATCHER_NAME },
-               { "host.name !~ 'host'",            -1,  MATCHER_NOT },
-               { "host.name = 'localhost' -- foo", -1,  MATCHER_NAME },
-               { "host.name = 'host' <garbage>",   18,  MATCHER_NAME },
+               { "host = 'localhost'",         -1,  MATCHER_NAME },
+               { "host != 'localhost'",        -1,  MATCHER_NOT },
+               { "host =~ 'host'",             -1,  MATCHER_NAME },
+               { "host !~ 'host'",             -1,  MATCHER_NOT },
+               { "host = 'localhost' -- foo",  -1,  MATCHER_NAME },
+               { "host = 'host' <garbage>",    13,  MATCHER_NAME },
                /* match hosts by service */
-               { "service.name = 'name'",          -1,  MATCHER_NAME },
-               { "service.name != 'name'",         -1,  MATCHER_NOT },
-               { "service.name =~ 'pattern'",      -1,  MATCHER_NAME },
-               { "service.name !~ 'pattern'",      -1,  MATCHER_NOT },
+               { "service = 'name'",           -1,  MATCHER_NAME },
+               { "service != 'name'",          -1,  MATCHER_NOT },
+               { "service =~ 'pattern'",       -1,  MATCHER_NAME },
+               { "service !~ 'pattern'",       -1,  MATCHER_NOT },
                /* match hosts by attribute */
-               { "attribute.name = 'name'",        -1,  MATCHER_NAME },
-               { "attribute.name != 'name'",       -1,  MATCHER_NOT },
-               { "attribute.name =~ 'pattern'",    -1,  MATCHER_NAME },
-               { "attribute.name !~ 'pattern'",    -1,  MATCHER_NOT },
+               { "attribute = 'name'",         -1,  MATCHER_NAME },
+               { "attribute != 'name'",        -1,  MATCHER_NOT },
+               { "attribute =~ 'pattern'",     -1,  MATCHER_NAME },
+               { "attribute !~ 'pattern'",     -1,  MATCHER_NOT },
                /* composite expressions */
-               { "host.name =~ 'pattern' AND "
-                 "service.name =~ 'pattern'",      -1,  MATCHER_AND },
-               { "host.name =~ 'pattern' OR "
-                 "service.name =~ 'pattern'",      -1,  MATCHER_OR },
-               { "NOT host.name = 'host'",         -1,  MATCHER_NOT },
+               { "host =~ 'pattern' AND "
+                 "service =~ 'pattern'",       -1,  MATCHER_AND },
+               { "host =~ 'pattern' OR "
+                 "service =~ 'pattern'",       -1,  MATCHER_OR },
+               { "NOT host = '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 },
+               /* datetime expressions */
+               { "attribute[foo] = "
+                 "2014-08-16",                 -1,  MATCHER_EQ },
+               { "attribute[foo] = "
+                 "17:23",                      -1,  MATCHER_EQ },
+               { "attribute[foo] = "
+                 "17:23:53",                   -1,  MATCHER_EQ },
+               { "attribute[foo] = "
+                 "17:23:53.123",               -1,  MATCHER_EQ },
+               { "attribute[foo] = "
+                 "17:23:53.123456789",         -1,  MATCHER_EQ },
+               { "attribute[foo] = "
+                 "2014-08-16 17:23",           -1,  MATCHER_EQ },
+               { "attribute[foo] = "
+                 "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_NOT },
+
+               /* object field matchers */
+               { ".last_update < 10s",         -1,  MATCHER_LT },
+               { ".AGE <= 1m",                 -1,  MATCHER_LE },
+               { ".interval = 10h",            -1,  MATCHER_EQ },
+               { ".Last_Update >= 24D",        -1,  MATCHER_GE },
+               { ".age > 1M",                  -1,  MATCHER_GT },
+               { ".age != 20Y",                -1,  MATCHER_NOT },
+               { ".backend != 'be'",           -1,  MATCHER_NOT },
+               { ".age <= 2 * .interval",      -1,  MATCHER_LE },
 
                /* check operator precedence */
-               { "host.name = 'name' OR "
-                 "service.name = 'name' AND "
-                 "attribute.name = 'name' OR "
-                 "attribute.foo = 'bar'",          -1,  MATCHER_OR },
-               { "host.name = 'name' AND "
-                 "service.name = 'name' AND "
-                 "attribute.name = 'name' OR "
-                 "attribute.foo = 'bar'",          -1,  MATCHER_OR },
-               { "host.name = 'name' AND "
-                 "service.name = 'name' OR "
-                 "attribute.name = 'name' AND "
-                 "attribute.foo = 'bar'",          -1,  MATCHER_OR },
-               { "(host.name = 'name' OR "
-                 "service.name = 'name') AND "
-                 "(attribute.name = 'name' OR "
-                 "attribute.foo = 'bar')",         -1,  MATCHER_AND },
-               { "NOT host.name = 'name' OR "
-                 "service.name = 'name'",          -1,  MATCHER_OR },
-               { "NOT host.name = 'name' OR "
-                 "NOT service.name = 'name'",      -1,  MATCHER_OR },
-               { "NOT (host.name = 'name' OR "
-                 "NOT service.name = 'name')",     -1,  MATCHER_NOT },
+               { "host = 'name' OR "
+                 "service = 'name' AND "
+                 "attribute = 'name' OR "
+                 "attribute[foo] = 'bar'",     -1,  MATCHER_OR },
+               { "host = 'name' AND "
+                 "service = 'name' AND "
+                 "attribute = 'name' OR "
+                 "attribute[foo] = 'bar'",     -1,  MATCHER_OR },
+               { "host = 'name' AND "
+                 "service = 'name' OR "
+                 "attribute = 'name' AND "
+                 "attribute[foo] = 'bar'",     -1,  MATCHER_OR },
+               { "(host = 'name' OR "
+                 "service = 'name') AND "
+                 "(attribute = 'name' OR "
+                 "attribute[foo] = 'bar')",    -1,  MATCHER_AND },
+               { "NOT host = 'name' OR "
+                 "service = 'name'",           -1,  MATCHER_OR },
+               { "NOT host = 'name' OR "
+                 "NOT service = 'name'",       -1,  MATCHER_OR },
+               { "NOT (host = 'name' OR "
+                 "NOT service = 'name')",      -1,  MATCHER_NOT },
 
                /* syntax errors */
-               { "LIST",                           -1, -1 },
-               { "foo &^ bar",                     -1, -1 },
+               { "LIST",                       -1, -1 },
+               { "foo &^ bar",                 -1, -1 },
        };
 
        size_t i;