Code

parser: Require a context for each parser operation.
authorSebastian Harl <sh@tokkee.org>
Mon, 31 Aug 2015 20:08:45 +0000 (22:08 +0200)
committerSebastian Harl <sh@tokkee.org>
Mon, 31 Aug 2015 20:08:45 +0000 (22:08 +0200)
That is, let the caller provide the execution context (object type) when
parsing incomplete commands (conditionals / arithmetic expressions). This
removes the need to call the analyzer again and avoids some special code in
the analyzer.

src/frontend/query.c
src/include/parser/parser.h
src/parser/analyzer.c
src/parser/parser.c
t/unit/core/store_expr_test.c
t/unit/core/store_lookup_test.c
t/unit/parser/parser_test.c

index 79886105bd3d68ed0dd099114af5d7d8913983cf..56cd4ec86638ef537d8bdc09fabc15e4b90ed0b0 100644 (file)
@@ -321,30 +321,22 @@ sdb_conn_lookup(sdb_conn_t *conn)
 
        matcher = sdb_strbuf_string(conn->buf) + sizeof(uint32_t);
        matcher_len = conn->cmd_len - sizeof(uint32_t);
-       m = sdb_parser_parse_conditional(matcher, (int)matcher_len, conn->errbuf);
+       m = sdb_parser_parse_conditional((int)type,
+                       matcher, (int)matcher_len, conn->errbuf);
        if (! m) {
-               char expr[matcher_len + 1];
-               strncpy(expr, matcher, sizeof(expr));
-               expr[sizeof(expr) - 1] = '\0';
-               sdb_log(SDB_LOG_ERR, "frontend: Failed to parse lookup condition '%s': %s",
-                               expr, sdb_strbuf_string(conn->errbuf));
-               return -1;
-       }
-
-       ast = sdb_ast_lookup_create((int)type, m, /* filter = */ NULL);
-       /* run analyzer using the full context */
-       if (ast && sdb_parser_analyze(ast, conn->errbuf)) {
                char expr[matcher_len + 1];
                char err[sdb_strbuf_len(conn->errbuf) + sizeof(expr) + 64];
                strncpy(expr, matcher, sizeof(expr));
                expr[sizeof(expr) - 1] = '\0';
                snprintf(err, sizeof(err), "Failed to parse lookup condition '%s': %s",
                                expr, sdb_strbuf_string(conn->errbuf));
+               sdb_log(SDB_LOG_ERR, "frontend: %s", err);
                sdb_strbuf_sprintf(conn->errbuf, "%s", err);
-               status = -1;
+               return -1;
        }
-       else
-               status = exec_query(conn, ast);
+
+       ast = sdb_ast_lookup_create((int)type, m, /* filter = */ NULL);
+       status = exec_query(conn, ast);
        if (! ast)
                sdb_object_deref(SDB_OBJ(m));
        sdb_object_deref(SDB_OBJ(ast));
index 2830663f03d8f13843eaa7543e1c18c50ebeccc5..a820bbef30a426cceb595224aca547e86ba3643e 100644 (file)
@@ -65,21 +65,25 @@ sdb_parser_parse(const char *query, int len, sdb_strbuf_t *errbuf);
 
 /*
  * sdb_parser_parse_conditional:
- * Parse a single conditional expression. This function is similar to
- * sdb_parse_parse but will only accept a single conditional expression. The
- * return value is guaranteed to satisfy SDB_AST_IS_LOGICAL().
+ * Parse a single conditional expression which can be evaluated in the
+ * specified context (any valid store object type). This function is similar
+ * to sdb_parse_parse but will only accept a single conditional expression.
+ * The return value is guaranteed to satisfy SDB_AST_IS_LOGICAL().
  */
 sdb_ast_node_t *
-sdb_parser_parse_conditional(const char *cond, int len, sdb_strbuf_t *errbuf);
+sdb_parser_parse_conditional(int context,
+               const char *cond, int len, sdb_strbuf_t *errbuf);
 
 /*
  * sdb_parser_parse_arith:
- * Parse a single arithmetic expression. This function is similar to
- * sdb_parse_parse but will only accept a single arithmetic expression. The
+ * Parse a single arithmetic expression which can be evaluated in the
+ * specified context (any valid store object type). This function is similar
+ * to sdb_parse_parse but will only accept a single arithmetic expression. The
  * return value is guaranteed to satisfy SDB_AST_IS_ARITHMETIC().
  */
 sdb_ast_node_t *
-sdb_parser_parse_arith(const char *expr, int len, sdb_strbuf_t *errbuf);
+sdb_parser_parse_arith(int context,
+               const char *expr, int len, sdb_strbuf_t *errbuf);
 
 /*
  * sdb_parser_analyze:
@@ -95,7 +99,8 @@ sdb_parser_analyze(sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
 
 /*
  * sdb_parser_analyze_conditional:
- * Semantical analysis of a conditional node.
+ * Semantical analysis of a conditional node in the specified context (any
+ * valid store object type).
  *
  * Returns:
  *  - 0 on success
@@ -103,11 +108,13 @@ sdb_parser_analyze(sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
  *    error buffer
  */
 int
-sdb_parser_analyze_conditional(sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
+sdb_parser_analyze_conditional(int context,
+               sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
 
 /*
  * sdb_parser_analyze_arith:
- * Semantical analysis of an arithmetic node.
+ * Semantical analysis of an arithmetic node in the specified context (any
+ * valid store object type).
  *
  * Returns:
  *  - 0 on success
@@ -115,7 +122,8 @@ sdb_parser_analyze_conditional(sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
  *    error buffer
  */
 int
-sdb_parser_analyze_arith(sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
+sdb_parser_analyze_arith(int context,
+               sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
 
 /*
  * Low-level interface.
index 9bbd9ba7c9fe31c1575a1160f3ffd218c6ba04fa..5b89016609e0aaa8aba20a6a4a9f5d648262fb80 100644 (file)
@@ -38,7 +38,6 @@
 #define VALID_OBJ_TYPE(t) ((SDB_HOST <= (t)) && ((t) <= SDB_METRIC))
 
 #define FILTER_CONTEXT -1
-#define UNSPEC_CONTEXT -2
 
 static int
 analyze_node(int context, sdb_ast_node_t *node, sdb_strbuf_t *errbuf);
@@ -234,7 +233,7 @@ analyze_iter(int context, sdb_ast_iter_t *iter, sdb_strbuf_t *errbuf)
                        /* attributes are always iterable */
                }
                else if ((context != SDB_HOST) && (context != SDB_SERVICE)
-                               && (context != SDB_METRIC) && (context != UNSPEC_CONTEXT)) {
+                               && (context != SDB_METRIC)) {
                        iter_error(errbuf, iter, "%s not iterable in %s context",
                                        SDB_STORE_TYPE_TO_NAME(iter_type),
                                        SDB_STORE_TYPE_TO_NAME(context));
@@ -319,20 +318,15 @@ analyze_value(int context, sdb_ast_value_t *v, sdb_strbuf_t *errbuf)
                return -1;
        }
 
-       if (context != UNSPEC_CONTEXT) {
-               /* skip these checks if we don't know the context; it's up to the
-                * caller to check again once the right context information is
-                * available */
-               if ((context != SDB_ATTRIBUTE) && (v->type == SDB_FIELD_VALUE)) {
-                       sdb_strbuf_sprintf(errbuf, "Invalid expression %s.value",
-                                       SDB_FIELD_TO_NAME(context));
-                       return -1;
-               }
-               if ((context != SDB_METRIC) && (v->type == SDB_FIELD_TIMESERIES)) {
-                       sdb_strbuf_sprintf(errbuf, "Invalid expression %s.timeseries",
-                                       SDB_FIELD_TO_NAME(context));
-                       return -1;
-               }
+       if ((context != SDB_ATTRIBUTE) && (v->type == SDB_FIELD_VALUE)) {
+               sdb_strbuf_sprintf(errbuf, "Invalid expression %s.value",
+                               SDB_FIELD_TO_NAME(context));
+               return -1;
+       }
+       if ((context != SDB_METRIC) && (v->type == SDB_FIELD_TIMESERIES)) {
+               sdb_strbuf_sprintf(errbuf, "Invalid expression %s.timeseries",
+                               SDB_FIELD_TO_NAME(context));
+               return -1;
        }
        return 0;
 } /* analyze_value */
@@ -609,8 +603,13 @@ sdb_parser_analyze(sdb_ast_node_t *node, sdb_strbuf_t *errbuf)
 } /* sdb_parser_analyze */
 
 int
-sdb_parser_analyze_conditional(sdb_ast_node_t *node, sdb_strbuf_t *errbuf)
+sdb_parser_analyze_conditional(int context,
+               sdb_ast_node_t *node, sdb_strbuf_t *errbuf)
 {
+       if (! VALID_OBJ_TYPE(context)) {
+               sdb_strbuf_sprintf(errbuf, "Invalid object type %#x", context);
+               return -1;
+       }
        if (! node) {
                sdb_strbuf_sprintf(errbuf, "Empty conditional node");
                return -1;
@@ -620,12 +619,17 @@ sdb_parser_analyze_conditional(sdb_ast_node_t *node, sdb_strbuf_t *errbuf)
                                SDB_AST_TYPE_TO_STRING(node));
                return -1;
        }
-       return analyze_node(UNSPEC_CONTEXT, node, errbuf);
+       return analyze_node(context, node, errbuf);
 } /* sdb_parser_analyze_conditional */
 
 int
-sdb_parser_analyze_arith(sdb_ast_node_t *node, sdb_strbuf_t *errbuf)
+sdb_parser_analyze_arith(int context,
+               sdb_ast_node_t *node, sdb_strbuf_t *errbuf)
 {
+       if (! VALID_OBJ_TYPE(context)) {
+               sdb_strbuf_sprintf(errbuf, "Invalid object type %#x", context);
+               return -1;
+       }
        if (! node) {
                sdb_strbuf_sprintf(errbuf, "Empty arithmetic node");
                return -1;
@@ -635,7 +639,7 @@ sdb_parser_analyze_arith(sdb_ast_node_t *node, sdb_strbuf_t *errbuf)
                                SDB_AST_TYPE_TO_STRING(node));
                return -1;
        }
-       return analyze_node(UNSPEC_CONTEXT, node, errbuf);
+       return analyze_node(context, node, errbuf);
 } /* sdb_parser_analyze_arith */
 
 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */
index 2e0b864a9132e78c644e6b3316fa5724afde327b..2216045b568891b507dc10ec4365f2c4f8de85ef 100644 (file)
@@ -107,7 +107,8 @@ sdb_parser_parse(const char *query, int len, sdb_strbuf_t *errbuf)
 } /* sdb_parser_parse */
 
 sdb_ast_node_t *
-sdb_parser_parse_conditional(const char *cond, int len, sdb_strbuf_t *errbuf)
+sdb_parser_parse_conditional(int context,
+               const char *cond, int len, sdb_strbuf_t *errbuf)
 {
        sdb_parser_yyscan_t scanner;
        sdb_parser_yyextra_t yyextra;
@@ -139,7 +140,7 @@ sdb_parser_parse_conditional(const char *cond, int len, sdb_strbuf_t *errbuf)
        assert(SDB_AST_IS_LOGICAL(node));
        sdb_llist_destroy(yyextra.parsetree);
 
-       if (sdb_parser_analyze_conditional(node, errbuf)) {
+       if (sdb_parser_analyze_conditional(context, node, errbuf)) {
                sdb_object_deref(SDB_OBJ(node));
                return NULL;
        }
@@ -147,7 +148,8 @@ sdb_parser_parse_conditional(const char *cond, int len, sdb_strbuf_t *errbuf)
 } /* sdb_parser_parse_conditional */
 
 sdb_ast_node_t *
-sdb_parser_parse_arith(const char *expr, int len, sdb_strbuf_t *errbuf)
+sdb_parser_parse_arith(int context,
+               const char *expr, int len, sdb_strbuf_t *errbuf)
 {
        sdb_parser_yyscan_t scanner;
        sdb_parser_yyextra_t yyextra;
@@ -179,7 +181,7 @@ sdb_parser_parse_arith(const char *expr, int len, sdb_strbuf_t *errbuf)
        assert(SDB_AST_IS_ARITHMETIC(node));
        sdb_llist_destroy(yyextra.parsetree);
 
-       if (sdb_parser_analyze_arith(node, errbuf)) {
+       if (sdb_parser_analyze_arith(context, node, errbuf)) {
                sdb_object_deref(SDB_OBJ(node));
                return NULL;
        }
index 7a918ce98347635222aa246642d7886d63f63ab6..303a921f0506ad952377ae58a7f7d13a12998d57 100644 (file)
@@ -581,7 +581,7 @@ START_TEST(test_expr_iter)
 
        if (expr_iter_data[_i].filter) {
                sdb_ast_node_t *ast;
-               ast = sdb_parser_parse_conditional(expr_iter_data[_i].filter, -1, NULL);
+               ast = sdb_parser_parse_conditional(context, expr_iter_data[_i].filter, -1, NULL);
                filter = sdb_store_query_prepare_matcher(ast);
                sdb_object_deref(SDB_OBJ(ast));
                ck_assert(filter != NULL);
index 9f486836aaec3daf85818ed07c7d17c67c327c63..d2c006494ab04f95cffce6fb2f4494dc6b740242 100644 (file)
@@ -641,20 +641,20 @@ START_TEST(test_scan)
        fail_unless(n == 3,
                        "sdb_store_scan called callback %d times; expected: 3", (int)n);
 
-       ast = sdb_parser_parse_conditional(scan_data[_i].query, -1, errbuf);
+       ast = sdb_parser_parse_conditional(SDB_HOST, scan_data[_i].query, -1, errbuf);
        m = sdb_store_query_prepare_matcher(ast);
        sdb_object_deref(SDB_OBJ(ast));
        fail_unless(m != NULL,
-                       "sdb_parser_parse_conditional(%s, -1) = NULL; expected: <ast> "
+                       "sdb_parser_parse_conditional(HOST, %s, -1) = NULL; expected: <ast> "
                        "(parser error: %s)", scan_data[_i].query,
                        sdb_strbuf_string(errbuf));
 
        if (scan_data[_i].filter) {
-               ast = sdb_parser_parse_conditional(scan_data[_i].filter, -1, errbuf);
+               ast = sdb_parser_parse_conditional(SDB_HOST, scan_data[_i].filter, -1, errbuf);
                filter = sdb_store_query_prepare_matcher(ast);
                sdb_object_deref(SDB_OBJ(ast));
                fail_unless(filter != NULL,
-                               "sdb_parser_parse_conditional(%s, -1) = NULL; "
+                               "sdb_parser_parse_conditional(HOST, %s, -1) = NULL; "
                                "expected: <ast> (parser error: %s)",
                                scan_data[_i].filter, sdb_strbuf_string(errbuf));
        }
index 3d2e40cbc4df0b4e6c59e0415c421d3d996ae716..62a7e9047a70ea4541ad5f6e8fab487846267930 100644 (file)
@@ -743,214 +743,215 @@ START_TEST(test_parse)
 END_TEST
 
 struct {
+       int context;
        const char *expr;
        int len;
        int expected;
 } parse_conditional_data[] = {
        /* empty expressions */
-       { NULL,                           -1, -1 },
-       { "",                             -1, -1 },
+       { SDB_HOST, NULL,                           -1, -1 },
+       { SDB_HOST, "",                             -1, -1 },
 
        /* match hosts by name */
-       { "name < 'localhost'",           -1,  SDB_AST_LT },
-       { "name <= 'localhost'",          -1,  SDB_AST_LE },
-       { "name = 'localhost'",           -1,  SDB_AST_EQ },
-       { "name != 'localhost'",          -1,  SDB_AST_NE },
-       { "name >= 'localhost'",          -1,  SDB_AST_GE },
-       { "name > 'localhost'",           -1,  SDB_AST_GT },
-       { "name =~ 'host'",               -1,  SDB_AST_REGEX },
-       { "name !~ 'host'",               -1,  SDB_AST_NREGEX },
-       { "name = 'localhost' -- foo",    -1,  SDB_AST_EQ },
-       { "name = 'host' <garbage>",      13,  SDB_AST_EQ },
-       { "name &^ 'localhost'",          -1,  -1 },
+       { SDB_HOST, "name < 'localhost'",           -1,  SDB_AST_LT },
+       { SDB_HOST, "name <= 'localhost'",          -1,  SDB_AST_LE },
+       { SDB_HOST, "name = 'localhost'",           -1,  SDB_AST_EQ },
+       { SDB_HOST, "name != 'localhost'",          -1,  SDB_AST_NE },
+       { SDB_HOST, "name >= 'localhost'",          -1,  SDB_AST_GE },
+       { SDB_HOST, "name > 'localhost'",           -1,  SDB_AST_GT },
+       { SDB_HOST, "name =~ 'host'",               -1,  SDB_AST_REGEX },
+       { SDB_HOST, "name !~ 'host'",               -1,  SDB_AST_NREGEX },
+       { SDB_HOST, "name = 'localhost' -- foo",    -1,  SDB_AST_EQ },
+       { SDB_HOST, "name = 'host' <garbage>",      13,  SDB_AST_EQ },
+       { SDB_HOST, "name &^ 'localhost'",          -1,  -1 },
        /* match by backend */
-       { "ANY backend < 'be'",           -1,  SDB_AST_ANY },
-       { "ANY backend <= 'be'",          -1,  SDB_AST_ANY },
-       { "ANY backend = 'be'",           -1,  SDB_AST_ANY },
-       { "ANY backend != 'be'",          -1,  SDB_AST_ANY },
-       { "ANY backend >= 'be'",          -1,  SDB_AST_ANY },
-       { "ANY backend > 'be'",           -1,  SDB_AST_ANY },
-       { "ALL backend < 'be'",           -1,  SDB_AST_ALL },
-       { "ALL backend <= 'be'",          -1,  SDB_AST_ALL },
-       { "ALL backend = 'be'",           -1,  SDB_AST_ALL },
-       { "ALL backend != 'be'",          -1,  SDB_AST_ALL },
-       { "ALL backend >= 'be'",          -1,  SDB_AST_ALL },
-       { "ALL backend > 'be'",           -1,  SDB_AST_ALL },
-       { "ANY backend &^ 'be'",          -1,  -1 },
+       { SDB_HOST, "ANY backend < 'be'",           -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY backend <= 'be'",          -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY backend = 'be'",           -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY backend != 'be'",          -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY backend >= 'be'",          -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY backend > 'be'",           -1,  SDB_AST_ANY },
+       { SDB_HOST, "ALL backend < 'be'",           -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL backend <= 'be'",          -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL backend = 'be'",           -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL backend != 'be'",          -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL backend >= 'be'",          -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL backend > 'be'",           -1,  SDB_AST_ALL },
+       { SDB_HOST, "ANY backend &^ 'be'",          -1,  -1 },
        /* match hosts by service */
-       { "ANY service.name < 'name'",         -1,  SDB_AST_ANY },
-       { "ANY service.name <= 'name'",        -1,  SDB_AST_ANY },
-       { "ANY service.name = 'name'",         -1,  SDB_AST_ANY },
-       { "ANY service.name != 'name'",        -1,  SDB_AST_ANY },
-       { "ANY service.name >= 'name'",        -1,  SDB_AST_ANY },
-       { "ANY service.name > 'name'",         -1,  SDB_AST_ANY },
-       { "ANY service.name =~ 'pattern'",     -1,  SDB_AST_ANY },
-       { "ANY service.name !~ 'pattern'",     -1,  SDB_AST_ANY },
-       { "ANY service.name &^ 'name'",        -1,  -1 },
-       { "ALL service.name < 'name'",         -1,  SDB_AST_ALL },
-       { "ALL service.name <= 'name'",        -1,  SDB_AST_ALL },
-       { "ALL service.name = 'name'",         -1,  SDB_AST_ALL },
-       { "ALL service.name != 'name'",        -1,  SDB_AST_ALL },
-       { "ALL service.name >= 'name'",        -1,  SDB_AST_ALL },
-       { "ALL service.name > 'name'",         -1,  SDB_AST_ALL },
-       { "ALL service.name =~ 'pattern'",     -1,  SDB_AST_ALL },
-       { "ALL service.name !~ 'pattern'",     -1,  SDB_AST_ALL },
-       { "ALL service.name &^ 'name'",        -1,  -1 },
-       { "ANY service < 'name'",              -1,  -1 },
+       { SDB_HOST, "ANY service.name < 'name'",         -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name <= 'name'",        -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name = 'name'",         -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name != 'name'",        -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name >= 'name'",        -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name > 'name'",         -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name =~ 'pattern'",     -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name !~ 'pattern'",     -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY service.name &^ 'name'",        -1,  -1 },
+       { SDB_HOST, "ALL service.name < 'name'",         -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name <= 'name'",        -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name = 'name'",         -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name != 'name'",        -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name >= 'name'",        -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name > 'name'",         -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name =~ 'pattern'",     -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name !~ 'pattern'",     -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL service.name &^ 'name'",        -1,  -1 },
+       { SDB_HOST, "ANY service < 'name'",              -1,  -1 },
        /* match hosts by metric */
-       { "ANY metric.name < 'name'",          -1,  SDB_AST_ANY },
-       { "ANY metric.name <= 'name'",         -1,  SDB_AST_ANY },
-       { "ANY metric.name = 'name'",          -1,  SDB_AST_ANY },
-       { "ANY metric.name != 'name'",         -1,  SDB_AST_ANY },
-       { "ANY metric.name >= 'name'",         -1,  SDB_AST_ANY },
-       { "ANY metric.name > 'name'",          -1,  SDB_AST_ANY },
-       { "ANY metric.name =~ 'pattern'",      -1,  SDB_AST_ANY },
-       { "ANY metric.name !~ 'pattern'",      -1,  SDB_AST_ANY },
-       { "ANY metric.name &^ 'pattern'",      -1,  -1 },
-       { "ALL metric.name < 'name'",          -1,  SDB_AST_ALL },
-       { "ALL metric.name <= 'name'",         -1,  SDB_AST_ALL },
-       { "ALL metric.name = 'name'",          -1,  SDB_AST_ALL },
-       { "ALL metric.name != 'name'",         -1,  SDB_AST_ALL },
-       { "ALL metric.name >= 'name'",         -1,  SDB_AST_ALL },
-       { "ALL metric.name > 'name'",          -1,  SDB_AST_ALL },
-       { "ALL metric.name =~ 'pattern'",      -1,  SDB_AST_ALL },
-       { "ALL metric.name !~ 'pattern'",      -1,  SDB_AST_ALL },
-       { "ALL metric.name &^ 'pattern'",      -1,  -1 },
-       { "ANY metric <= 'name'",              -1,  -1 },
+       { SDB_HOST, "ANY metric.name < 'name'",          -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name <= 'name'",         -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name = 'name'",          -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name != 'name'",         -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name >= 'name'",         -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name > 'name'",          -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name =~ 'pattern'",      -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name !~ 'pattern'",      -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY metric.name &^ 'pattern'",      -1,  -1 },
+       { SDB_HOST, "ALL metric.name < 'name'",          -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name <= 'name'",         -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name = 'name'",          -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name != 'name'",         -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name >= 'name'",         -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name > 'name'",          -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name =~ 'pattern'",      -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name !~ 'pattern'",      -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL metric.name &^ 'pattern'",      -1,  -1 },
+       { SDB_HOST, "ANY metric <= 'name'",              -1,  -1 },
        /* match hosts by attribute */
-       { "ANY attribute.name < 'name'",       -1,  SDB_AST_ANY },
-       { "ANY attribute.name <= 'name'",      -1,  SDB_AST_ANY },
-       { "ANY attribute.name = 'name'",       -1,  SDB_AST_ANY },
-       { "ANY attribute.name != 'name'",      -1,  SDB_AST_ANY },
-       { "ANY attribute.name >= 'name'",      -1,  SDB_AST_ANY },
-       { "ANY attribute.name > 'name'",       -1,  SDB_AST_ANY },
-       { "ANY attribute.name =~ 'pattern'",   -1,  SDB_AST_ANY },
-       { "ANY attribute.name !~ 'pattern'",   -1,  SDB_AST_ANY },
-       { "ANY attribute.name &^ 'pattern'",   -1,  -1 },
-       { "ALL attribute.name < 'name'",       -1,  SDB_AST_ALL },
-       { "ALL attribute.name <= 'name'",      -1,  SDB_AST_ALL },
-       { "ALL attribute.name = 'name'",       -1,  SDB_AST_ALL },
-       { "ALL attribute.name != 'name'",      -1,  SDB_AST_ALL },
-       { "ALL attribute.name >= 'name'",      -1,  SDB_AST_ALL },
-       { "ALL attribute.name > 'name'",       -1,  SDB_AST_ALL },
-       { "ALL attribute.name =~ 'pattern'",   -1,  SDB_AST_ALL },
-       { "ALL attribute.name !~ 'pattern'",   -1,  SDB_AST_ALL },
-       { "ALL attribute.name &^ 'pattern'",   -1,  -1 },
-       { "ANY attribute !~ 'pattern'",        -1,  -1 },
+       { SDB_HOST, "ANY attribute.name < 'name'",       -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name <= 'name'",      -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name = 'name'",       -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name != 'name'",      -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name >= 'name'",      -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name > 'name'",       -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name =~ 'pattern'",   -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name !~ 'pattern'",   -1,  SDB_AST_ANY },
+       { SDB_HOST, "ANY attribute.name &^ 'pattern'",   -1,  -1 },
+       { SDB_HOST, "ALL attribute.name < 'name'",       -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name <= 'name'",      -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name = 'name'",       -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name != 'name'",      -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name >= 'name'",      -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name > 'name'",       -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name =~ 'pattern'",   -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name !~ 'pattern'",   -1,  SDB_AST_ALL },
+       { SDB_HOST, "ALL attribute.name &^ 'pattern'",   -1,  -1 },
+       { SDB_HOST, "ANY attribute !~ 'pattern'",        -1,  -1 },
 
        /* composite expressions */
-       { "name =~ 'pattern' AND "
+       { SDB_HOST, "name =~ 'pattern' AND "
          "ANY service.name =~ 'pattern'",  -1,  SDB_AST_AND },
-       { "name =~ 'pattern' OR "
+       { SDB_HOST, "name =~ 'pattern' OR "
          "ANY service.name =~ 'pattern'",  -1,  SDB_AST_OR },
-       { "NOT name = 'host'",              -1,  SDB_AST_NOT },
+       { SDB_HOST, "NOT name = 'host'",              -1,  SDB_AST_NOT },
        /* numeric expressions */
-       { "attribute['foo'] < 123",         -1,  SDB_AST_LT },
-       { "attribute['foo'] <= 123",        -1,  SDB_AST_LE },
-       { "attribute['foo'] = 123",         -1,  SDB_AST_EQ },
-       { "attribute['foo'] >= 123",        -1,  SDB_AST_GE },
-       { "attribute['foo'] > 123",         -1,  SDB_AST_GT },
+       { SDB_HOST, "attribute['foo'] < 123",         -1,  SDB_AST_LT },
+       { SDB_HOST, "attribute['foo'] <= 123",        -1,  SDB_AST_LE },
+       { SDB_HOST, "attribute['foo'] = 123",         -1,  SDB_AST_EQ },
+       { SDB_HOST, "attribute['foo'] >= 123",        -1,  SDB_AST_GE },
+       { SDB_HOST, "attribute['foo'] > 123",         -1,  SDB_AST_GT },
        /* datetime expressions */
-       { "attribute['foo'] = "
+       { SDB_HOST, "attribute['foo'] = "
          "2014-08-16",                     -1,  SDB_AST_EQ },
-       { "attribute['foo'] = "
+       { SDB_HOST, "attribute['foo'] = "
          "17:23",                          -1,  SDB_AST_EQ },
-       { "attribute['foo'] = "
+       { SDB_HOST, "attribute['foo'] = "
          "17:23:53",                       -1,  SDB_AST_EQ },
-       { "attribute['foo'] = "
+       { SDB_HOST, "attribute['foo'] = "
          "17:23:53.123",                   -1,  SDB_AST_EQ },
-       { "attribute['foo'] = "
+       { SDB_HOST, "attribute['foo'] = "
          "17:23:53.123456789",             -1,  SDB_AST_EQ },
-       { "attribute['foo'] = "
+       { SDB_HOST, "attribute['foo'] = "
          "2014-08-16 17:23",               -1,  SDB_AST_EQ },
-       { "attribute['foo'] = "
+       { SDB_HOST, "attribute['foo'] = "
          "2014-08-16 17:23:53",            -1,  SDB_AST_EQ },
        /* NULL / TRUE / FALSE */
-       { "attribute['foo'] IS NULL",       -1,  SDB_AST_ISNULL },
-       { "attribute['foo'] IS NOT NULL",   -1,  SDB_AST_NOT },
-       { "attribute['foo'] IS TRUE",       -1,  SDB_AST_ISTRUE },
-       { "attribute['foo'] IS NOT TRUE",   -1,  SDB_AST_NOT },
-       { "attribute['foo'] IS FALSE",      -1,  SDB_AST_ISFALSE },
-       { "attribute['foo'] IS NOT FALSE",  -1,  SDB_AST_NOT },
+       { SDB_HOST, "attribute['foo'] IS NULL",       -1,  SDB_AST_ISNULL },
+       { SDB_HOST, "attribute['foo'] IS NOT NULL",   -1,  SDB_AST_NOT },
+       { SDB_HOST, "attribute['foo'] IS TRUE",       -1,  SDB_AST_ISTRUE },
+       { SDB_HOST, "attribute['foo'] IS NOT TRUE",   -1,  SDB_AST_NOT },
+       { SDB_HOST, "attribute['foo'] IS FALSE",      -1,  SDB_AST_ISFALSE },
+       { SDB_HOST, "attribute['foo'] IS NOT FALSE",  -1,  SDB_AST_NOT },
        /* array expressions */
-       { "backend < ['a']",                -1,  SDB_AST_LT },
-       { "backend <= ['a']",               -1,  SDB_AST_LE },
-       { "backend = ['a']",                -1,  SDB_AST_EQ },
-       { "backend != ['a']",               -1,  SDB_AST_NE },
-       { "backend >= ['a']",               -1,  SDB_AST_GE },
-       { "backend > ['a']",                -1,  SDB_AST_GT },
-       { "backend &^ ['a']",               -1,  -1 },
+       { SDB_HOST, "backend < ['a']",                -1,  SDB_AST_LT },
+       { SDB_HOST, "backend <= ['a']",               -1,  SDB_AST_LE },
+       { SDB_HOST, "backend = ['a']",                -1,  SDB_AST_EQ },
+       { SDB_HOST, "backend != ['a']",               -1,  SDB_AST_NE },
+       { SDB_HOST, "backend >= ['a']",               -1,  SDB_AST_GE },
+       { SDB_HOST, "backend > ['a']",                -1,  SDB_AST_GT },
+       { SDB_HOST, "backend &^ ['a']",               -1,  -1 },
 
        /* object field comparison */
-       { "name < 'a'",                     -1,  SDB_AST_LT },
-       { "name <= 'a'",                    -1,  SDB_AST_LE },
-       { "name = 'a'",                     -1,  SDB_AST_EQ },
-       { "name != 'a'",                    -1,  SDB_AST_NE },
-       { "name >= 'a'",                    -1,  SDB_AST_GE },
-       { "name > 'a'",                     -1,  SDB_AST_GT },
-       { "last_update < 2014-10-01",       -1,  SDB_AST_LT },
-       { "last_update <= 2014-10-01",      -1,  SDB_AST_LE },
-       { "last_update = 2014-10-01",       -1,  SDB_AST_EQ },
-       { "last_update != 2014-10-01",      -1,  SDB_AST_NE },
-       { "last_update >= 2014-10-01",      -1,  SDB_AST_GE },
-       { "last_update > 2014-10-01",       -1,  SDB_AST_GT },
-       { "Last_Update >= 24D",             -1,  SDB_AST_GE },
-       { "age < 20s",                      -1,  SDB_AST_LT },
-       { "age <= 20s",                     -1,  SDB_AST_LE },
-       { "age = 20s",                      -1,  SDB_AST_EQ },
-       { "age != 20s",                     -1,  SDB_AST_NE },
-       { "age >= 20s",                     -1,  SDB_AST_GE },
-       { "age > 20s",                      -1,  SDB_AST_GT },
-       { "AGE <= 1m",                      -1,  SDB_AST_LE },
-       { "age > 1M",                       -1,  SDB_AST_GT },
-       { "age != 20Y",                     -1,  SDB_AST_NE },
-       { "age <= 2 * interval",            -1,  SDB_AST_LE },
-       { "interval < 20s",                 -1,  SDB_AST_LT },
-       { "interval <= 20s",                -1,  SDB_AST_LE },
-       { "interval = 20s",                 -1,  SDB_AST_EQ },
-       { "interval != 20s",                -1,  SDB_AST_NE },
-       { "interval >= 20s",                -1,  SDB_AST_GE },
-       { "interval > 20s",                 -1,  SDB_AST_GT },
-       { "'be' IN backend",                -1,  SDB_AST_IN },
-       { "'be' NOT IN backend",            -1,  SDB_AST_NOT },
-       { "['a','b'] IN backend",           -1,  SDB_AST_IN },
-       { "['a','b'] NOT IN backend",       -1,  SDB_AST_NOT },
-       { "timeseries IS TRUE",             -1,  SDB_AST_ISTRUE },
-       { "timeseries IS FALSE",            -1,  SDB_AST_ISFALSE },
-       { "timeseries IS NOT TRUE",         -1,  SDB_AST_NOT },
-       { "timeseries IS NOT FALSE",        -1,  SDB_AST_NOT },
-       { "timeseries > 0",                 -1,  -1 },
-       { "timeseries = TRUE",              -1,  -1 },
-       { "timeseries != FALSE",            -1,  -1 },
+       { SDB_HOST, "name < 'a'",                     -1,  SDB_AST_LT },
+       { SDB_HOST, "name <= 'a'",                    -1,  SDB_AST_LE },
+       { SDB_HOST, "name = 'a'",                     -1,  SDB_AST_EQ },
+       { SDB_HOST, "name != 'a'",                    -1,  SDB_AST_NE },
+       { SDB_HOST, "name >= 'a'",                    -1,  SDB_AST_GE },
+       { SDB_HOST, "name > 'a'",                     -1,  SDB_AST_GT },
+       { SDB_HOST, "last_update < 2014-10-01",       -1,  SDB_AST_LT },
+       { SDB_HOST, "last_update <= 2014-10-01",      -1,  SDB_AST_LE },
+       { SDB_HOST, "last_update = 2014-10-01",       -1,  SDB_AST_EQ },
+       { SDB_HOST, "last_update != 2014-10-01",      -1,  SDB_AST_NE },
+       { SDB_HOST, "last_update >= 2014-10-01",      -1,  SDB_AST_GE },
+       { SDB_HOST, "last_update > 2014-10-01",       -1,  SDB_AST_GT },
+       { SDB_HOST, "Last_Update >= 24D",             -1,  SDB_AST_GE },
+       { SDB_HOST, "age < 20s",                      -1,  SDB_AST_LT },
+       { SDB_HOST, "age <= 20s",                     -1,  SDB_AST_LE },
+       { SDB_HOST, "age = 20s",                      -1,  SDB_AST_EQ },
+       { SDB_HOST, "age != 20s",                     -1,  SDB_AST_NE },
+       { SDB_HOST, "age >= 20s",                     -1,  SDB_AST_GE },
+       { SDB_HOST, "age > 20s",                      -1,  SDB_AST_GT },
+       { SDB_HOST, "AGE <= 1m",                      -1,  SDB_AST_LE },
+       { SDB_HOST, "age > 1M",                       -1,  SDB_AST_GT },
+       { SDB_HOST, "age != 20Y",                     -1,  SDB_AST_NE },
+       { SDB_HOST, "age <= 2 * interval",            -1,  SDB_AST_LE },
+       { SDB_HOST, "interval < 20s",                 -1,  SDB_AST_LT },
+       { SDB_HOST, "interval <= 20s",                -1,  SDB_AST_LE },
+       { SDB_HOST, "interval = 20s",                 -1,  SDB_AST_EQ },
+       { SDB_HOST, "interval != 20s",                -1,  SDB_AST_NE },
+       { SDB_HOST, "interval >= 20s",                -1,  SDB_AST_GE },
+       { SDB_HOST, "interval > 20s",                 -1,  SDB_AST_GT },
+       { SDB_HOST, "'be' IN backend",                -1,  SDB_AST_IN },
+       { SDB_HOST, "'be' NOT IN backend",            -1,  SDB_AST_NOT },
+       { SDB_HOST, "['a','b'] IN backend",           -1,  SDB_AST_IN },
+       { SDB_HOST, "['a','b'] NOT IN backend",       -1,  SDB_AST_NOT },
+       { SDB_METRIC, "timeseries IS TRUE",           -1,  SDB_AST_ISTRUE },
+       { SDB_METRIC, "timeseries IS FALSE",          -1,  SDB_AST_ISFALSE },
+       { SDB_METRIC, "timeseries IS NOT TRUE",       -1,  SDB_AST_NOT },
+       { SDB_METRIC, "timeseries IS NOT FALSE",      -1,  SDB_AST_NOT },
+       { SDB_METRIC, "timeseries > 0",               -1,  -1 },
+       { SDB_METRIC, "timeseries = TRUE",            -1,  -1 },
+       { SDB_METRIC, "timeseries != FALSE",          -1,  -1 },
 
        /* check operator precedence */
-       { "name = 'name' OR "
+       { SDB_HOST, "name = 'name' OR "
          "ANY service.name = 'name' AND "
          "ANY attribute.name = 'name' OR "
          "attribute['foo'] = 'bar'",       -1,  SDB_AST_OR },
-       { "name = 'name' AND "
+       { SDB_HOST, "name = 'name' AND "
          "ANY service.name = 'name' AND "
          "ANY attribute.name = 'name' OR "
          "attribute['foo'] = 'bar'",       -1,  SDB_AST_OR },
-       { "name = 'name' AND "
+       { SDB_HOST, "name = 'name' AND "
          "ANY service.name = 'name' OR "
          "ANY attribute.name = 'name' AND "
          "attribute['foo'] = 'bar'",       -1,  SDB_AST_OR },
-       { "(name = 'name' OR "
+       { SDB_HOST, "(name = 'name' OR "
          "ANY service.name = 'name') AND "
          "(ANY attribute.name = 'name' OR "
          "attribute['foo'] = 'bar')",      -1,  SDB_AST_AND },
-       { "NOT name = 'name' OR "
+       { SDB_HOST, "NOT name = 'name' OR "
          "ANY service.name = 'name'",      -1,  SDB_AST_OR },
-       { "NOT name = 'name' OR "
+       { SDB_HOST, "NOT name = 'name' OR "
          "NOT ANY service.name = 'name'",  -1,  SDB_AST_OR },
-       { "NOT (name = 'name' OR "
+       { SDB_HOST, "NOT (name = 'name' OR "
          "NOT ANY service.name = 'name')", -1,  SDB_AST_NOT },
 
        /* syntax errors */
-       { "LIST hosts",                     -1, -1 },
-       { "foo &^ bar",                     -1, -1 },
-       { "invalid",                        -1, -1 },
+       { SDB_HOST, "LIST hosts",                     -1, -1 },
+       { SDB_HOST, "foo &^ bar",                     -1, -1 },
+       { SDB_HOST, "invalid",                        -1, -1 },
 };
 
 START_TEST(test_parse_conditional)
@@ -958,35 +959,43 @@ START_TEST(test_parse_conditional)
        sdb_strbuf_t *errbuf = sdb_strbuf_create(64);
        sdb_ast_node_t *node;
 
-       node = sdb_parser_parse_conditional(parse_conditional_data[_i].expr,
-                       parse_conditional_data[_i].len, errbuf);
+       node = sdb_parser_parse_conditional(parse_conditional_data[_i].context,
+                       parse_conditional_data[_i].expr, parse_conditional_data[_i].len, errbuf);
 
        if (parse_conditional_data[_i].expected < 0) {
                fail_unless(node == NULL,
-                               "sdb_parser_parse_conditional(%s) = %p; expected: NULL",
+                               "sdb_parser_parse_conditional(%s, %s) = %p; expected: NULL",
+                               SDB_STORE_TYPE_TO_NAME(parse_conditional_data[_i].context),
                                parse_conditional_data[_i].expr, node);
                sdb_object_deref(SDB_OBJ(node));
                sdb_strbuf_destroy(errbuf);
                return;
        }
 
-       fail_unless(node != NULL, "sdb_parser_parse_conditional(%s) = NULL; "
+       fail_unless(node != NULL, "sdb_parser_parse_conditional(%s, %s) = NULL; "
                        "expected: <cond> (parse error: %s)",
+                       SDB_STORE_TYPE_TO_NAME(parse_conditional_data[_i].context),
                        parse_conditional_data[_i].expr, sdb_strbuf_string(errbuf));
        if (node->type == SDB_AST_TYPE_OPERATOR)
                fail_unless(SDB_AST_OP(node)->kind == parse_conditional_data[_i].expected,
-                               "sdb_parser_parse_conditional(%s) returned conditional of type %d; "
-                               "expected: %d", parse_conditional_data[_i].expr,
+                               "sdb_parser_parse_conditional(%s, %s) returned conditional of type %d; "
+                               "expected: %d",
+                               SDB_STORE_TYPE_TO_NAME(parse_conditional_data[_i].context),
+                               parse_conditional_data[_i].expr,
                                SDB_AST_OP(node)->kind, parse_conditional_data[_i].expected);
        else if (node->type == SDB_AST_TYPE_ITERATOR)
                fail_unless(SDB_AST_ITER(node)->kind == parse_conditional_data[_i].expected,
-                               "sdb_parser_parse_conditional(%s) returned conditional of type %d; "
-                               "expected: %d", parse_conditional_data[_i].expr,
+                               "sdb_parser_parse_conditional(%s, %s) returned conditional of type %d; "
+                               "expected: %d",
+                               SDB_STORE_TYPE_TO_NAME(parse_conditional_data[_i].context),
+                               parse_conditional_data[_i].expr,
                                SDB_AST_ITER(node)->kind, parse_conditional_data[_i].expected);
 
        fail_unless(node->data_type == -1,
-                       "sdb_parser_parse_conditional(%s) returned conditional of data-type %s; "
-                       "expected: %s", parse_conditional_data[_i].expr,
+                       "sdb_parser_parse_conditional(%s, %s) returned conditional "
+                       "of data-type %s; expected: %s",
+                       SDB_STORE_TYPE_TO_NAME(parse_conditional_data[_i].context),
+                       parse_conditional_data[_i].expr,
                        SDB_TYPE_TO_STRING(node->data_type), SDB_TYPE_TO_STRING(-1));
 
        sdb_object_deref(SDB_OBJ(node));
@@ -995,81 +1004,82 @@ START_TEST(test_parse_conditional)
 END_TEST
 
 struct {
+       int context;
        const char *expr;
        int len;
        int expected;
        int data_type;
 } parse_arith_data[] = {
        /* empty expressions */
-       { NULL,                   -1, -1, -1 },
-       { "",                     -1, -1, -1 },
+       { SDB_HOST, NULL,                   -1, -1, -1 },
+       { SDB_HOST, "",                     -1, -1, -1 },
 
        /* constant expressions */
-       { "'localhost'",          -1, SDB_AST_TYPE_CONST, SDB_TYPE_STRING },
-       { "123",                  -1, SDB_AST_TYPE_CONST, SDB_TYPE_INTEGER },
-       { "42.3",                 -1, SDB_AST_TYPE_CONST, SDB_TYPE_DECIMAL },
-       { "2014-08-16",           -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "17:23",                -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "17:23:53",             -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "17:23:53.123",         -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "17:23:53.123456789",   -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "2014-08-16 17:23",     -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "2014-08-16 17:23:53",  -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "10s",                  -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "60m",                  -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
-       { "10Y 24D 1h",           -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "'localhost'",          -1, SDB_AST_TYPE_CONST, SDB_TYPE_STRING },
+       { SDB_HOST, "123",                  -1, SDB_AST_TYPE_CONST, SDB_TYPE_INTEGER },
+       { SDB_HOST, "42.3",                 -1, SDB_AST_TYPE_CONST, SDB_TYPE_DECIMAL },
+       { SDB_HOST, "2014-08-16",           -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "17:23",                -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "17:23:53",             -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "17:23:53.123",         -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "17:23:53.123456789",   -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "2014-08-16 17:23",     -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "2014-08-16 17:23:53",  -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "10s",                  -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "60m",                  -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
+       { SDB_HOST, "10Y 24D 1h",           -1, SDB_AST_TYPE_CONST, SDB_TYPE_DATETIME },
 
        /* TODO: the analyzer and/or optimizer should turn these into constants */
-       { "123 + 456",            -1, SDB_AST_ADD, SDB_TYPE_INTEGER },
-       { "'foo' || 'bar'",       -1, SDB_AST_CONCAT, SDB_TYPE_STRING },
-       { "456 - 123",            -1, SDB_AST_SUB, SDB_TYPE_INTEGER },
-       { "1.2 * 3.4",            -1, SDB_AST_MUL, SDB_TYPE_DECIMAL },
-       { "1.2 / 3.4",            -1, SDB_AST_DIV, SDB_TYPE_DECIMAL },
-       { "5 % 2",                -1, SDB_AST_MOD, SDB_TYPE_INTEGER },
+       { SDB_HOST, "123 + 456",            -1, SDB_AST_ADD, SDB_TYPE_INTEGER },
+       { SDB_HOST, "'foo' || 'bar'",       -1, SDB_AST_CONCAT, SDB_TYPE_STRING },
+       { SDB_HOST, "456 - 123",            -1, SDB_AST_SUB, SDB_TYPE_INTEGER },
+       { SDB_HOST, "1.2 * 3.4",            -1, SDB_AST_MUL, SDB_TYPE_DECIMAL },
+       { SDB_HOST, "1.2 / 3.4",            -1, SDB_AST_DIV, SDB_TYPE_DECIMAL },
+       { SDB_HOST, "5 % 2",                -1, SDB_AST_MOD, SDB_TYPE_INTEGER },
 
        /* queryable fields */
-       { "last_update",          -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
-       { "AGE",                  -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
-       { "interval",             -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
-       { "Last_Update",          -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
-       { "backend",              -1, SDB_AST_TYPE_VALUE, SDB_TYPE_ARRAY | SDB_TYPE_STRING },
+       { SDB_HOST, "last_update",          -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
+       { SDB_HOST, "AGE",                  -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
+       { SDB_HOST, "interval",             -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
+       { SDB_HOST, "Last_Update",          -1, SDB_AST_TYPE_VALUE, SDB_TYPE_DATETIME },
+       { SDB_HOST, "backend",              -1, SDB_AST_TYPE_VALUE, SDB_TYPE_ARRAY | SDB_TYPE_STRING },
 
        /* attributes */
-       { "attribute['foo']",     -1, SDB_AST_TYPE_VALUE, -1 },
+       { SDB_HOST, "attribute['foo']",     -1, SDB_AST_TYPE_VALUE, -1 },
 
        /* arithmetic expressions */
-       { "age + age",            -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
-       { "age - age",            -1, SDB_AST_SUB, SDB_TYPE_DATETIME },
-       { "age * age",            -1, SDB_AST_MUL, SDB_TYPE_DATETIME },
-       { "age / age",            -1, SDB_AST_DIV, SDB_TYPE_DATETIME },
-       { "age \% age",           -1, SDB_AST_MOD, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age + age",            -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age - age",            -1, SDB_AST_SUB, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age * age",            -1, SDB_AST_MUL, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age / age",            -1, SDB_AST_DIV, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age \% age",           -1, SDB_AST_MOD, SDB_TYPE_DATETIME },
 
        /* operator precedence */
-       { "age + age * age",      -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
-       { "age * age + age",      -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
-       { "age + age - age",      -1, SDB_AST_SUB, SDB_TYPE_DATETIME },
-       { "age - age + age",      -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
-       { "(age + age) * age",    -1, SDB_AST_MUL, SDB_TYPE_DATETIME },
-       { "age + (age * age)",    -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age + age * age",      -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age * age + age",      -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age + age - age",      -1, SDB_AST_SUB, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age - age + age",      -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
+       { SDB_HOST, "(age + age) * age",    -1, SDB_AST_MUL, SDB_TYPE_DATETIME },
+       { SDB_HOST, "age + (age * age)",    -1, SDB_AST_ADD, SDB_TYPE_DATETIME },
 
        /* boolean expressions */
-       { "timeseries + 1",               -1, -1, -1 },
-       { "timeseries - 1",               -1, -1, -1 },
-       { "timeseries * 1",               -1, -1, -1 },
-       { "timeseries / 1",               -1, -1, -1 },
-       { "timeseries \% 1",              -1, -1, -1 },
-       { "timeseries CONCAT 1",          -1, -1, -1 },
-       { "timeseries + timeseries",      -1, -1, -1 },
-       { "timeseries - timeseries",      -1, -1, -1 },
-       { "timeseries * timeseries",      -1, -1, -1 },
-       { "timeseries / timeseries",      -1, -1, -1 },
-       { "timeseries \% timeseries",     -1, -1, -1 },
-       { "timeseries CONCAT timeseries", -1, -1, -1 },
+       { SDB_METRIC, "timeseries + 1",               -1, -1, -1 },
+       { SDB_METRIC, "timeseries - 1",               -1, -1, -1 },
+       { SDB_METRIC, "timeseries * 1",               -1, -1, -1 },
+       { SDB_METRIC, "timeseries / 1",               -1, -1, -1 },
+       { SDB_METRIC, "timeseries \% 1",              -1, -1, -1 },
+       { SDB_METRIC, "timeseries CONCAT 1",          -1, -1, -1 },
+       { SDB_METRIC, "timeseries + timeseries",      -1, -1, -1 },
+       { SDB_METRIC, "timeseries - timeseries",      -1, -1, -1 },
+       { SDB_METRIC, "timeseries * timeseries",      -1, -1, -1 },
+       { SDB_METRIC, "timeseries / timeseries",      -1, -1, -1 },
+       { SDB_METRIC, "timeseries \% timeseries",     -1, -1, -1 },
+       { SDB_METRIC, "timeseries CONCAT timeseries", -1, -1, -1 },
 
        /* syntax errors */
-       { "LIST",                 -1, -1, -1 },
-       { "foo &^ bar",           -1, -1, -1 },
-       { "invalid",              -1, -1, -1 },
+       { SDB_HOST, "LIST",                 -1, -1, -1 },
+       { SDB_HOST, "foo &^ bar",           -1, -1, -1 },
+       { SDB_HOST, "invalid",              -1, -1, -1 },
 };
 
 START_TEST(test_parse_arith)
@@ -1077,36 +1087,40 @@ START_TEST(test_parse_arith)
        sdb_strbuf_t *errbuf = sdb_strbuf_create(64);
        sdb_ast_node_t *node;
 
-       node = sdb_parser_parse_arith(parse_arith_data[_i].expr,
-                       parse_arith_data[_i].len, errbuf);
+       node = sdb_parser_parse_arith(parse_arith_data[_i].context,
+                       parse_arith_data[_i].expr, parse_arith_data[_i].len, errbuf);
 
        if (parse_arith_data[_i].expected < 0) {
                fail_unless(node == NULL,
-                               "sdb_parser_parse_arith(%s) = %p; expected: NULL",
+                               "sdb_parser_parse_arith(%s, %s) = %p; expected: NULL",
+                               SDB_STORE_TYPE_TO_NAME(parse_arith_data[_i].context),
                                parse_arith_data[_i].expr, node);
                sdb_object_deref(SDB_OBJ(node));
                sdb_strbuf_destroy(errbuf);
                return;
        }
 
-       fail_unless(node != NULL, "sdb_parser_parse_arith(%s) = NULL; "
+       fail_unless(node != NULL, "sdb_parser_parse_arith(%s, %s) = NULL; "
                        "expected: <expr> (parse error: %s)",
+                       SDB_STORE_TYPE_TO_NAME(parse_arith_data[_i].context),
                        parse_arith_data[_i].expr, sdb_strbuf_string(errbuf));
        if (node->type == SDB_AST_TYPE_OPERATOR)
                fail_unless(SDB_AST_OP(node)->kind == parse_arith_data[_i].expected,
-                               "sdb_parser_parse_arith(%s) returned expression of type %d; "
-                               "expected: %d", parse_arith_data[_i].expr,
-                               SDB_AST_OP(node)->kind, parse_arith_data[_i].expected);
+                               "sdb_parser_parse_arith(%s, %s) returned expression of type %d; "
+                               "expected: %d", SDB_STORE_TYPE_TO_NAME(parse_arith_data[_i].context),
+                               parse_arith_data[_i].expr, SDB_AST_OP(node)->kind,
+                               parse_arith_data[_i].expected);
        else
                fail_unless(node->type == parse_arith_data[_i].expected,
-                               "sdb_parser_parse_arith(%s) returned expression of type %d; "
-                               "expected: %d", parse_arith_data[_i].expr, node->type,
+                               "sdb_parser_parse_arith(%s, %s) returned expression of type %d; "
+                               "expected: %d", SDB_STORE_TYPE_TO_NAME(parse_arith_data[_i].context),
+                               parse_arith_data[_i].expr, node->type,
                                parse_arith_data[_i].expected);
 
        fail_unless(node->data_type == parse_arith_data[_i].data_type,
-                       "sdb_parser_parse_arith(%s) returned expression of data-type %s; "
-                       "expected: %s", parse_arith_data[_i].expr,
-                       SDB_TYPE_TO_STRING(node->data_type),
+                       "sdb_parser_parse_arith(%s, %s) returned expression of data-type %s; "
+                       "expected: %s", SDB_STORE_TYPE_TO_NAME(parse_arith_data[_i].context),
+                       parse_arith_data[_i].expr, SDB_TYPE_TO_STRING(node->data_type),
                        SDB_TYPE_TO_STRING(parse_arith_data[_i].data_type));
 
        sdb_object_deref(SDB_OBJ(node));