From: Sebastian Harl Date: Sun, 12 Apr 2015 13:12:45 +0000 (+0200) Subject: Migrate parser from frontend/ to parser/ and to use the AST. X-Git-Tag: sysdb-0.8.0~113 X-Git-Url: https://git.tokkee.org/?p=sysdb.git;a=commitdiff_plain;h=c8d51914ef24f01f935d2853d57f05d0b0a0af9e Migrate parser from frontend/ to parser/ and to use the AST. The new parser now returns AST nodes instead of a "compiled" store lookup operator. The old parser is still in place unmodified and currently still in use. --- diff --git a/.gitignore b/.gitignore index 002520f..aa77a2c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ src/frontend/scanner.c src/liboconfig/parser.c src/liboconfig/parser.h src/liboconfig/scanner.c +src/parser/grammar.c +src/parser/grammar.h +src/parser/scanner.c src/tools/sysdb/scanner.c src/sysdb src/sysdbd diff --git a/src/Makefile.am b/src/Makefile.am index a7f6c9b..5a3146a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,7 +12,7 @@ AM_CPPFLAGS += -DPKGLIBDIR='"${pkglibdir}"' AM_YFLAGS = -d BUILT_SOURCES = include/client/sysdb.h include/sysdb.h \ - frontend/grammar.h + frontend/grammar.h parser/grammar.h EXTRA_DIST = include/client/sysdb.h.in include/sysdb.h.in pkginclude_HEADERS = include/sysdb.h @@ -68,7 +68,8 @@ libsysdbclient_la_LIBADD = $(LIBLTDL) @OPENSSL_LIBS@ # don't use strict CFLAGS for flex code noinst_LTLIBRARIES += libsysdb_fe_parser.la libsysdb_fe_parser_la_SOURCES = \ - frontend/grammar.y frontend/scanner.l + frontend/grammar.y frontend/scanner.l \ + parser/grammar.y parser/scanner.l libsysdb_fe_parser_la_CFLAGS = @COVERAGE_CFLAGS@ @PROFILING_CFLAGS@ \ -DBUILD_DATE="\"$$( date --utc '+%F %T' ) (UTC)\"" libsysdb_la_SOURCES = \ @@ -92,6 +93,7 @@ libsysdb_la_SOURCES = \ frontend/store.c \ frontend/query.c \ parser/ast.c include/parser/ast.h \ + parser/parser.c include/parser/parser.h \ utils/avltree.c include/utils/avltree.h \ utils/channel.c include/utils/channel.h \ utils/error.c include/utils/error.h \ diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h new file mode 100644 index 0000000..9235ef5 --- /dev/null +++ b/src/include/parser/parser.h @@ -0,0 +1,131 @@ +/* + * SysDB - src/include/parser/parser.h + * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SDB_PARSER_PARSER_H +#define SDB_PARSER_PARSER_H 1 + +/* TODO: move SDB_PARSE_* constants here as well */ +#include "frontend/parser.h" + +#include "core/store.h" +#include "parser/ast.h" +#include "utils/llist.h" +#include "utils/strbuf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * sdb_parser_parse: + * Parse the specified query of the specified length. If len is a negative + * value, use the entire string. + * + * Returns: + * - a list of AST nodes (sdb_ast_node_t) on success; each node describes one + * statement of the query + * - NULL else; an error message will be written to the specified error + * buffer + */ +sdb_llist_t * +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(). + */ +sdb_ast_node_t * +sdb_parser_parse_conditional(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 + * 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); + +/* + * Low-level interface. + */ + +/* scanner/parser's YY_EXTRA data */ +typedef struct { + /* list of sdb_ast_node_t objects */ + sdb_llist_t *parsetree; + + /* parser mode */ + int mode; + + /* buffer for parser error messages */ + sdb_strbuf_t *errbuf; +} sdb_parser_yyextra_t; + +/* see yyscan_t */ +typedef void *sdb_parser_yyscan_t; + +/* + * sdb_parser_scanner_init: + * Allocate and initialize a scanner object. It will operate on the specified + * string of the specified length. If len is less than zero, use the entire + * string. The scanner/parser extra data stores shared state information + * between the scanner and the parser. + */ +sdb_parser_yyscan_t +sdb_parser_scanner_init(const char *str, int len, sdb_parser_yyextra_t *yyext); + +/* + * sdb_parser_scanner_destroy: + * Destroy a scanner object freeing all of its memory. + */ +void +sdb_parser_scanner_destroy(sdb_parser_yyscan_t scanner); + +/* + * sdb_parser_yyparse: + * Invoke the low-level parser using the specified scanner. The result will be + * returned through the scanner/parser's extra data. + * + * Returns: + * - 0 on success + * - a non-zero value else; the error buffer stored in the scanner/parser's + * extra data provides an error message in this case + */ +int +sdb_parser_yyparse(sdb_parser_yyscan_t scanner); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ! SDB_PARSER_PARSER_H */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/parser/grammar.y b/src/parser/grammar.y new file mode 100644 index 0000000..bdba4ea --- /dev/null +++ b/src/parser/grammar.y @@ -0,0 +1,752 @@ +/* + * SysDB - src/parser/grammar.y + * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Grammar for the SysDB Query Language (SysQL). + */ + +%{ + +#include "core/store.h" +#include "core/time.h" + +#include "parser/ast.h" +#include "parser/parser.h" +#include "parser/grammar.h" + +#include "utils/error.h" +#include "utils/llist.h" + +#include + +#include +#include + +/* + * public API + */ + +int +sdb_parser_yylex(YYSTYPE *yylval, YYLTYPE *yylloc, sdb_parser_yyscan_t yyscanner); + +sdb_parser_yyextra_t * +sdb_parser_yyget_extra(sdb_parser_yyscan_t scanner); + +void +sdb_parser_yyerror(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *msg); +void +sdb_parser_yyerrorf(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *fmt, ...); + +/* quick access to the current parse tree */ +#define pt sdb_parser_yyget_extra(scanner)->parsetree + +/* quick access to the parser mode */ +#define parser_mode sdb_parser_yyget_extra(scanner)->mode + +/* quick access to the parser's error buffer */ +#define errbuf sdb_parser_yyget_extra(scanner)->errbuf + +#define CK_OOM(p) \ + do { \ + if (! (p)) { \ + sdb_parser_yyerror(&yylloc, scanner, YY_("out of memory")); \ + YYABORT; \ + } \ + } while (0) + +#define MODE_TO_STRING(m) \ + (((m) == SDB_PARSE_DEFAULT) ? "statement" \ + : ((m) == SDB_PARSE_COND) ? "condition" \ + : ((m) == SDB_PARSE_ARITH) ? "arithmetic expression" \ + : "UNKNOWN") + +%} + +%pure-parser +%lex-param {sdb_parser_yyscan_t scanner} +%parse-param {sdb_parser_yyscan_t scanner} +%locations +%error-verbose +%expect 0 +%name-prefix "sdb_parser_yy" + +%union { + char *str; + int integer; + + sdb_data_t data; + sdb_time_t datetime; + + sdb_llist_t *list; + sdb_ast_node_t *node; + + struct { char *type; char *id; } metric_store; +} + +%start statements + +%token SCANNER_ERROR + +%token AND OR IS NOT MATCHING FILTER +%token CMP_EQUAL CMP_NEQUAL CMP_REGEX CMP_NREGEX +%token CMP_LT CMP_LE CMP_GE CMP_GT ALL ANY IN +%token CONCAT + +%token HOST_T HOSTS_T SERVICE_T SERVICES_T METRIC_T METRICS_T +%token ATTRIBUTE_T ATTRIBUTES_T +%token NAME_T LAST_UPDATE_T AGE_T INTERVAL_T BACKEND_T VALUE_T + +%token LAST UPDATE + +%token START END + +/* NULL token */ +%token NULL_T + +%token FETCH LIST LOOKUP STORE TIMESERIES + +%token IDENTIFIER STRING + +%token INTEGER FLOAT + +%token DATE TIME + +/* Precedence (lowest first): */ +%left OR +%left AND +%right NOT +%left CMP_EQUAL CMP_NEQUAL +%left CMP_LT CMP_LE CMP_GE CMP_GT +%nonassoc CMP_REGEX CMP_NREGEX +%nonassoc IN +%left CONCAT +%nonassoc IS +%left '+' '-' +%left '*' '/' '%' +%left '[' ']' +%left '(' ')' +%left '.' + +%type statements +%type statement + fetch_statement + list_statement + lookup_statement + store_statement + timeseries_statement + matching_clause + filter_clause + condition comparison + expression object_expression + +%type object_type object_type_plural +%type field +%type cmp + +%type data + interval interval_elem + array array_elem_list + +%type datetime + start_clause end_clause + last_update_clause + +%type metric_store_clause + +%destructor { free($$); } +%destructor { sdb_object_deref(SDB_OBJ($$)); } +%destructor { sdb_data_free_datum(&$$); } + +%% + +statements: + statements ';' statement + { + /* only accepted in default parse mode */ + if (parser_mode != SDB_PARSE_DEFAULT) { + sdb_parser_yyerrorf(&yylloc, scanner, + YY_("syntax error, unexpected statement, " + "expecting %s"), MODE_TO_STRING(parser_mode)); + sdb_object_deref(SDB_OBJ($3)); + YYABORT; + } + + if ($3) { + sdb_llist_append(pt, SDB_OBJ($3)); + sdb_object_deref(SDB_OBJ($3)); + } + } + | + statement + { + /* only accepted in default parse mode */ + if (parser_mode != SDB_PARSE_DEFAULT) { + sdb_parser_yyerrorf(&yylloc, scanner, + YY_("syntax error, unexpected statement, " + "expecting %s"), MODE_TO_STRING(parser_mode)); + sdb_object_deref(SDB_OBJ($1)); + YYABORT; + } + + if ($1) { + sdb_llist_append(pt, SDB_OBJ($1)); + sdb_object_deref(SDB_OBJ($1)); + } + } + | + condition + { + /* only accepted in condition parse mode */ + if (! (parser_mode & SDB_PARSE_COND)) { + sdb_parser_yyerrorf(&yylloc, scanner, + YY_("syntax error, unexpected condition, " + "expecting %s"), MODE_TO_STRING(parser_mode)); + sdb_object_deref(SDB_OBJ($1)); + YYABORT; + } + + if ($1) { + sdb_llist_append(pt, SDB_OBJ($1)); + sdb_object_deref(SDB_OBJ($1)); + } + } + | + expression + { + /* only accepted in expression parse mode */ + if (! (parser_mode & SDB_PARSE_ARITH)) { + sdb_parser_yyerrorf(&yylloc, scanner, + YY_("syntax error, unexpected expression, " + "expecting %s"), MODE_TO_STRING(parser_mode)); + sdb_object_deref(SDB_OBJ($1)); + YYABORT; + } + + if ($1) { + sdb_llist_append(pt, SDB_OBJ($1)); + sdb_object_deref(SDB_OBJ($1)); + } + } + ; + +statement: + fetch_statement + | + list_statement + | + lookup_statement + | + store_statement + | + timeseries_statement + | + /* empty */ + { + $$ = NULL; + } + ; + +/* + * FETCH host [FILTER ]; + * FETCH . [FILTER ]; + * + * Retrieve detailed information about a single object. + */ +fetch_statement: + FETCH object_type STRING filter_clause + { + $$ = sdb_ast_fetch_create($2, $3, NULL, $4); + CK_OOM($$); + } + | + FETCH object_type STRING '.' STRING filter_clause + { + $$ = sdb_ast_fetch_create($2, $3, $5, $6); + CK_OOM($$); + } + ; + +/* + * LIST [FILTER ]; + * + * Returns a list of all objects in the store. + */ +list_statement: + LIST object_type_plural filter_clause + { + $$ = sdb_ast_list_create($2, $3); + CK_OOM($$); + } + ; + +/* + * LOOKUP [MATCHING ] [FILTER ]; + * + * Returns detailed information about objects matching a condition. + */ +lookup_statement: + LOOKUP object_type_plural matching_clause filter_clause + { + $$ = sdb_ast_lookup_create($2, $3, $4); + CK_OOM($$); + } + ; + +matching_clause: + MATCHING condition { $$ = $2; } + | + /* empty */ { $$ = NULL; } + +filter_clause: + FILTER condition { $$ = $2; } + | + /* empty */ { $$ = NULL; } + +/* + * STORE |. [LAST UPDATE ]; + * STORE METRIC . STORE [LAST UPDATE ]; + * STORE ATTRIBUTE . [LAST UPDATE ]; + * + * Store or update an object in the database. + */ +store_statement: + STORE HOST_T STRING last_update_clause + { + $$ = sdb_ast_store_create(SDB_HOST, NULL, 0, NULL, + $3, $4, NULL, NULL, SDB_DATA_NULL); + CK_OOM($$); + } + | + STORE SERVICE_T STRING '.' STRING last_update_clause + { + $$ = sdb_ast_store_create(SDB_SERVICE, $3, 0, NULL, + $5, $6, NULL, NULL, SDB_DATA_NULL); + CK_OOM($$); + } + | + STORE METRIC_T STRING '.' STRING metric_store_clause last_update_clause + { + $$ = sdb_ast_store_create(SDB_METRIC, $3, 0, NULL, + $5, $7, $6.type, $6.id, SDB_DATA_NULL); + CK_OOM($$); + } + | + STORE HOST_T ATTRIBUTE_T STRING '.' STRING data last_update_clause + { + $$ = sdb_ast_store_create(SDB_ATTRIBUTE, $4, 0, NULL, + $6, $8, NULL, NULL, $7); + CK_OOM($$); + } + | + STORE SERVICE_T ATTRIBUTE_T STRING '.' STRING '.' STRING data last_update_clause + { + $$ = sdb_ast_store_create(SDB_ATTRIBUTE, $4, SDB_SERVICE, $6, + $8, $10, NULL, NULL, $9); + CK_OOM($$); + } + | + STORE METRIC_T ATTRIBUTE_T STRING '.' STRING '.' STRING data last_update_clause + { + $$ = sdb_ast_store_create(SDB_ATTRIBUTE, $4, SDB_METRIC, $6, + $8, $10, NULL, NULL, $9); + CK_OOM($$); + } + ; + +last_update_clause: + LAST UPDATE datetime { $$ = $3; } + | + /* empty */ { $$ = sdb_gettime(); } + +metric_store_clause: + STORE STRING STRING { $$.type = $2; $$.id = $3; } + | + /* empty */ { $$.type = $$.id = NULL; } + +/* + * TIMESERIES . [START ] [END ]; + * + * Returns a time-series for the specified host's metric. + */ +timeseries_statement: + TIMESERIES STRING '.' STRING start_clause end_clause + { + $$ = sdb_ast_timeseries_create($2, $4, $5, $6); + CK_OOM($$); + } + ; + +start_clause: + START datetime { $$ = $2; } + | + /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; } + +end_clause: + END datetime { $$ = $2; } + | + /* empty */ { $$ = sdb_gettime(); } + +/* + * Basic expressions. + */ + +condition: + '(' condition ')' + { + $$ = $2; + } + | + condition AND condition + { + $$ = sdb_ast_op_create(SDB_AST_AND, $1, $3); + CK_OOM($$); + } + | + condition OR condition + { + $$ = sdb_ast_op_create(SDB_AST_OR, $1, $3); + CK_OOM($$); + } + | + NOT condition + { + $$ = sdb_ast_op_create(SDB_AST_NOT, NULL, $2); + CK_OOM($$); + } + | + comparison + { + $$ = $1; + } + ; + +comparison: + expression cmp expression + { + $$ = sdb_ast_op_create($2, $1, $3); + CK_OOM($$); + } + | + ANY expression cmp expression + { + $$ = sdb_ast_iter_create(SDB_AST_ANY, $3, $2, $4); + CK_OOM($$); + } + | + ALL expression cmp expression + { + $$ = sdb_ast_iter_create(SDB_AST_ALL, $3, $2, $4); + CK_OOM($$); + } + | + expression IS NULL_T + { + $$ = sdb_ast_op_create(SDB_AST_ISNULL, NULL, $1); + CK_OOM($$); + } + | + expression IS NOT NULL_T + { + $$ = sdb_ast_op_create(SDB_AST_ISNULL, NULL, $1); + CK_OOM($$); + $$ = sdb_ast_op_create(SDB_AST_NOT, NULL, $$); + CK_OOM($$); + } + | + expression IN expression + { + $$ = sdb_ast_op_create(SDB_AST_IN, $1, $3); + CK_OOM($$); + } + | + expression NOT IN expression + { + $$ = sdb_ast_op_create(SDB_AST_IN, $1, $4); + CK_OOM($$); + $$ = sdb_ast_op_create(SDB_AST_NOT, NULL, $$); + CK_OOM($$); + } + ; + +expression: + '(' expression ')' + { + $$ = $2; + } + | + expression '+' expression + { + $$ = sdb_ast_op_create(SDB_AST_ADD, $1, $3); + CK_OOM($$); + } + | + expression '-' expression + { + $$ = sdb_ast_op_create(SDB_AST_SUB, $1, $3); + CK_OOM($$); + } + | + expression '*' expression + { + $$ = sdb_ast_op_create(SDB_AST_MUL, $1, $3); + CK_OOM($$); + } + | + expression '/' expression + { + $$ = sdb_ast_op_create(SDB_AST_DIV, $1, $3); + CK_OOM($$); + } + | + expression '%' expression + { + $$ = sdb_ast_op_create(SDB_AST_MOD, $1, $3); + CK_OOM($$); + } + | + expression CONCAT expression + { + $$ = sdb_ast_op_create(SDB_AST_CONCAT, $1, $3); + CK_OOM($$); + } + | + object_expression + { + $$ = $1; + } + | + data + { + $$ = sdb_ast_const_create($1); + CK_OOM($$); + } + ; + +object_expression: + object_type '.' object_expression + { + $$ = sdb_ast_typed_create($1, $3); + CK_OOM($$); + } + | + ATTRIBUTE_T '.' object_expression + { + $$ = sdb_ast_typed_create(SDB_ATTRIBUTE, $3); + CK_OOM($$); + } + | + field + { + $$ = sdb_ast_value_create($1, NULL); + CK_OOM($$); + } + | + ATTRIBUTE_T '[' STRING ']' + { + $$ = sdb_ast_value_create(SDB_ATTRIBUTE, $3); + CK_OOM($$); + } + ; + +object_type: + HOST_T { $$ = SDB_HOST; } + | + SERVICE_T { $$ = SDB_SERVICE; } + | + METRIC_T { $$ = SDB_METRIC; } + ; + +object_type_plural: + HOSTS_T { $$ = SDB_HOST; } + | + SERVICES_T { $$ = SDB_SERVICE; } + | + METRICS_T { $$ = SDB_METRIC; } + ; + +field: + NAME_T { $$ = SDB_FIELD_NAME; } + | + LAST_UPDATE_T { $$ = SDB_FIELD_LAST_UPDATE; } + | + AGE_T { $$ = SDB_FIELD_AGE; } + | + INTERVAL_T { $$ = SDB_FIELD_INTERVAL; } + | + BACKEND_T { $$ = SDB_FIELD_BACKEND; } + | + VALUE_T { $$ = SDB_FIELD_VALUE; } + ; + +cmp: + CMP_EQUAL { $$ = SDB_AST_EQ; } + | + CMP_NEQUAL { $$ = SDB_AST_NE; } + | + CMP_REGEX { $$ = SDB_AST_REGEX; } + | + CMP_NREGEX { $$ = SDB_AST_NREGEX; } + | + CMP_LT { $$ = SDB_AST_LT; } + | + CMP_LE { $$ = SDB_AST_LE; } + | + CMP_GE { $$ = SDB_AST_GE; } + | + CMP_GT { $$ = SDB_AST_GT; } + ; + +data: + STRING { $$.type = SDB_TYPE_STRING; $$.data.string = $1; } + | + INTEGER { $$ = $1; } + | + FLOAT { $$ = $1; } + | + datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; } + | + interval { $$ = $1; } + | + array { $$ = $1; } + ; + +datetime: + DATE TIME { $$ = $1 + $2; } + | + DATE { $$ = $1; } + | + TIME { $$ = $1; } + ; + +interval: + interval interval_elem + { + $$.data.datetime = $1.data.datetime + $2.data.datetime; + } + | + interval_elem { $$ = $1; } + ; + +interval_elem: + INTEGER IDENTIFIER + { + sdb_time_t unit = sdb_strpunit($2); + if (! unit) { + sdb_parser_yyerrorf(&yylloc, scanner, + YY_("syntax error, invalid time unit %s"), $2); + free($2); $2 = NULL; + YYABORT; + } + free($2); $2 = NULL; + + if ($1.data.integer < 0) { + sdb_parser_yyerror(&yylloc, scanner, + YY_("syntax error, negative intervals not supported")); + YYABORT; + } + + $$.type = SDB_TYPE_DATETIME; + $$.data.datetime = (sdb_time_t)$1.data.integer * unit; + } + ; + +array: + '[' array_elem_list ']' + { + $$ = $2; + } + ; + +array_elem_list: + array_elem_list ',' data + { + size_t elem_size = sdb_data_sizeof($3.type); + + if (($3.type & SDB_TYPE_ARRAY) || (($1.type & 0xff) != $3.type)) { + sdb_parser_yyerrorf(&yylloc, scanner, YY_("syntax error, " + "cannot use element of type %s in array of type %s"), + SDB_TYPE_TO_STRING($3.type), + SDB_TYPE_TO_STRING($1.type)); + sdb_data_free_datum(&$1); + sdb_data_free_datum(&$3); + YYABORT; + } + + $$ = $1; + $$.data.array.values = realloc($$.data.array.values, + ($$.data.array.length + 1) * elem_size); + CK_OOM($$.data.array.values); + + memcpy((char *)$$.data.array.values + $$.data.array.length * elem_size, + &$3.data, elem_size); + ++$$.data.array.length; + } + | + data + { + size_t elem_size = sdb_data_sizeof($1.type); + + if ($1.type & SDB_TYPE_ARRAY) { + sdb_parser_yyerrorf(&yylloc, scanner, YY_("syntax error, " + "cannot construct array of type %s"), + SDB_TYPE_TO_STRING($1.type)); + sdb_data_free_datum(&$1); + YYABORT; + } + + $$ = $1; + $$.type |= SDB_TYPE_ARRAY; + $$.data.array.values = malloc(elem_size); + CK_OOM($$.data.array.values); + + memcpy($$.data.array.values, &$1.data, elem_size); + $$.data.array.length = 1; + } + ; + +%% + +void +sdb_parser_yyerror(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *msg) +{ + sdb_log(SDB_LOG_ERR, "parser: parse error: %s", msg); + sdb_strbuf_sprintf(errbuf, "%s", msg); +} /* sdb_parser_yyerror */ + +void +sdb_parser_yyerrorf(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *fmt, ...) +{ + va_list ap, aq; + va_start(ap, fmt); + va_copy(aq, ap); + sdb_vlog(SDB_LOG_ERR, fmt, ap); + sdb_strbuf_vsprintf(errbuf, fmt, aq); + va_end(ap); +} /* sdb_parser_yyerrorf */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/parser/parser.c b/src/parser/parser.c new file mode 100644 index 0000000..d22ce5f --- /dev/null +++ b/src/parser/parser.c @@ -0,0 +1,185 @@ +/* + * SysDB - src/parser/parser.c + * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sysdb.h" + +#include "core/store.h" + +#include "parser/ast.h" +#include "parser/parser.h" +#include "parser/grammar.h" + +#include "utils/llist.h" +#include "utils/strbuf.h" + +#include +#include + +/* + * private helper functions + */ + +static int +scanner_init(const char *input, int len, + sdb_parser_yyscan_t *scanner, sdb_parser_yyextra_t *extra, + sdb_strbuf_t *errbuf) +{ + if (! input) { + sdb_strbuf_sprintf(errbuf, "Missing scanner input"); + return -1; + } + + memset(extra, 0, sizeof(*extra)); + extra->parsetree = sdb_llist_create(); + extra->mode = SDB_PARSE_DEFAULT; + extra->errbuf = errbuf; + + if (! extra->parsetree) { + sdb_strbuf_sprintf(errbuf, "Failed to allocate parse-tree"); + return -1; + } + + *scanner = sdb_parser_scanner_init(input, len, extra); + if (! *scanner) { + sdb_llist_destroy(extra->parsetree); + return -1; + } + return 0; +} /* scanner_init */ + +/* + * public API + */ + +sdb_llist_t * +sdb_parser_parse(const char *query, int len, sdb_strbuf_t *errbuf) +{ + sdb_parser_yyscan_t scanner; + sdb_parser_yyextra_t yyextra; + sdb_llist_iter_t *iter; + int yyres; + + if (scanner_init(query, len, &scanner, &yyextra, errbuf)) + return NULL; + + yyres = sdb_parser_yyparse(scanner); + sdb_parser_scanner_destroy(scanner); + + if (yyres) { + sdb_llist_destroy(yyextra.parsetree); + return NULL; + } + + iter = sdb_llist_get_iter(yyextra.parsetree); + while (sdb_llist_iter_has_next(iter)) { + sdb_conn_node_t *node; + node = SDB_CONN_NODE(sdb_llist_iter_get_next(iter)); + assert(node); + /* TODO + if (sdb_parser_analyze(node, errbuf)) { + sdb_llist_iter_destroy(iter); + sdb_llist_destroy(yyextra.parsetree); + return NULL; + } + */ + } + sdb_llist_iter_destroy(iter); + return yyextra.parsetree; +} /* sdb_parser_parse */ + +sdb_ast_node_t * +sdb_parser_parse_conditional(const char *cond, int len, sdb_strbuf_t *errbuf) +{ + sdb_parser_yyscan_t scanner; + sdb_parser_yyextra_t yyextra; + + sdb_ast_node_t *node; + + int yyres; + + if (scanner_init(cond, len, &scanner, &yyextra, errbuf)) + return NULL; + + yyextra.mode = SDB_PARSE_COND; + + yyres = sdb_parser_yyparse(scanner); + sdb_parser_scanner_destroy(scanner); + + if (yyres) { + sdb_llist_destroy(yyextra.parsetree); + return NULL; + } + + node = SDB_AST_NODE(sdb_llist_get(yyextra.parsetree, 0)); + if (! node) { + sdb_strbuf_sprintf(errbuf, "Empty conditional expression '%s'", cond); + sdb_llist_destroy(yyextra.parsetree); + return NULL; + } + + assert(SDB_AST_IS_LOGICAL(node)); + sdb_llist_destroy(yyextra.parsetree); + return node; +} /* sdb_parser_parse_conditional */ + +sdb_ast_node_t * +sdb_parser_parse_arith(const char *expr, int len, sdb_strbuf_t *errbuf) +{ + sdb_parser_yyscan_t scanner; + sdb_parser_yyextra_t yyextra; + + sdb_ast_node_t *node; + + int yyres; + + if (scanner_init(expr, len, &scanner, &yyextra, errbuf)) + return NULL; + + yyextra.mode = SDB_PARSE_ARITH; + + yyres = sdb_parser_yyparse(scanner); + sdb_parser_scanner_destroy(scanner); + + if (yyres) { + sdb_llist_destroy(yyextra.parsetree); + return NULL; + } + + node = SDB_AST_NODE(sdb_llist_get(yyextra.parsetree, 0)); + if (! node) { + sdb_strbuf_sprintf(errbuf, "Empty expression '%s'", expr); + sdb_llist_destroy(yyextra.parsetree); + return NULL; + } + + assert(SDB_AST_IS_ARITHMETIC(node)); + sdb_llist_destroy(yyextra.parsetree); + return node; +} /* sdb_parser_parse_arith */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/parser/scanner.l b/src/parser/scanner.l new file mode 100644 index 0000000..43ec844 --- /dev/null +++ b/src/parser/scanner.l @@ -0,0 +1,308 @@ +/* + * SysDB - src/parser/scanner.l + * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +%{ + +#if HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "core/data.h" + +#include "parser/parser.h" +#include "parser/grammar.h" + +#include "utils/error.h" + +#include +#include + +#include +#include + +#include + +#define YY_EXTRA_TYPE sdb_parser_yyextra_t * + +static struct { + const char *name; + int id; +} reserved_words[] = { + { "ALL", ALL }, + { "AND", AND }, + { "ANY", ANY }, + { "END", END }, + { "FETCH", FETCH }, + { "FILTER", FILTER }, + { "IN", IN }, + { "IS", IS }, + { "LAST", LAST }, + { "LIST", LIST }, + { "LOOKUP", LOOKUP }, + { "MATCHING", MATCHING }, + { "NOT", NOT }, + { "NULL", NULL_T }, + { "OR", OR }, + { "START", START }, + { "STORE", STORE }, + { "TIMESERIES", TIMESERIES }, + { "UPDATE", UPDATE }, + + /* object types */ + { "host", HOST_T }, + { "hosts", HOSTS_T }, + { "service", SERVICE_T }, + { "services", SERVICES_T }, + { "metric", METRIC_T }, + { "metrics", METRICS_T }, + { "attribute", ATTRIBUTE_T }, + { "attributes", ATTRIBUTES_T }, + /* queryable fields */ + { "name", NAME_T }, + { "last_update", LAST_UPDATE_T }, + { "age", AGE_T }, + { "interval", INTERVAL_T }, + { "backend", BACKEND_T }, + { "value", VALUE_T }, +}; + +void +sdb_parser_yyerror(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *msg); + +%} + +%option never-interactive +%option reentrant +%option bison-bridge +%option bison-locations +%option 8bit +%option yylineno +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option verbose +%option warn +%option prefix="sdb_parser_yy" outfile="lex.yy.c" + +%x CSC + +whitespace ([ \t\n\r\f]+) +simple_comment ("--"[^\n\r]*) + +/* + * C style comments + */ +csc_start \/\* +csc_inside ([^*/]+|[^*]\/|\*[^/]) +csc_end \*\/ + +/* + * Strings and identifiers. + */ +identifier ([A-Za-z_][A-Za-z_0-9$]*) +/* TODO: fully support SQL strings */ +string ('([^']|'')*') + +/* + * Numeric constants. + */ +dec ([\+\-]?[0-9]+) +exp ([\+\-]?[0-9]+[Ee]\+?[0-9]+) +integer ({dec}|{exp}) +float1 ([\+\-]?[0-9]+\.[0-9]*([Ee][\+\-]?[0-9]+)?) +float2 ([\+\-]?[0-9]*\.[0-9]+([Ee][\+\-]?[0-9]+)?) +float3 ([\+\-]?[0-9]+[Ee]\-[0-9]+) +float4 ([\+\-]?[Ii][Nn][Ff]([Ii][Nn][Ii][Tt][Yy])?) +float5 ([Nn][Aa][Nn]) +float ({float1}|{float2}|{float3}|{float4}|{float5}) + +/* + * Time constants. + */ +date ([0-9]{4}-[0-9]{2}-[0-9]{2}) +time ([0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2}(\.[0-9]{1,9})?)?) + +%% + +{whitespace} | +{simple_comment} { /* ignore */ } + +{csc_start} { BEGIN(CSC); } +{csc_inside} { /* ignore */ } +{csc_end} { BEGIN(INITIAL); } +<> { + sdb_parser_yyerror(yylloc, yyscanner, "unterminated C-style comment"); + return SCANNER_ERROR; + } + +{identifier} { + size_t i; + for (i = 0; i < SDB_STATIC_ARRAY_LEN(reserved_words); ++i) + if (! strcasecmp(reserved_words[i].name, yytext)) + return reserved_words[i].id; + + yylval->str = strdup(yytext); + return IDENTIFIER; + } +{string} { + char *quot; + size_t len; + + /* remove the leading and trailing quote */ + yytext[yyleng - 1] = '\0'; + yylval->str = strdup(yytext + 1); + + quot = yylval->str; + len = yyleng - 2; + while ((quot = strstr(quot, "''")) != NULL) { + memmove(quot, quot + 1, len - (quot - yylval->str) - 1); + yylval->str[len - 1] = '\0'; + --len; + ++quot; + } + return STRING; + } +{integer} { + yylval->data.data.integer = (int64_t)strtoll(yytext, NULL, 10); + yylval->data.type = SDB_TYPE_INTEGER; + return INTEGER; + } +{float} { + yylval->data.data.decimal = strtod(yytext, NULL); + yylval->data.type = SDB_TYPE_DECIMAL; + return FLOAT; + } + +{date} { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + if (! strptime(yytext, "%Y-%m-%d", &tm)) { + char errmsg[1024]; + snprintf(errmsg, sizeof(errmsg), + "Failed to parse '%s' as date", yytext); + sdb_parser_yyerror(yylloc, yyscanner, errmsg); + return SCANNER_ERROR; + } + yylval->datetime = SECS_TO_SDB_TIME(mktime(&tm)); + return DATE; + } +{time} { + struct tm tm; + char time[9], ns[10]; + char *tmp; + int t; + + memset(&tm, 0, sizeof(tm)); + memset(time, '\0', sizeof(time)); + memset(ns, '0', sizeof(ns)); + ns[sizeof(ns) - 1] = '\0'; + + tmp = strchr(yytext, '.'); + if (tmp) { + size_t i; + *tmp = '\0'; + ++tmp; + strncpy(ns, tmp, sizeof(ns)); + for (i = strlen(ns); i < 9; ++i) + ns[i] = '0'; + } + strncpy(time, yytext, sizeof(time)); + if (tmp) { + /* reset for better error messages */ + --tmp; + *tmp = '.'; + } + + tmp = strchr(time, ':'); + assert(tmp); + tmp = strchr(tmp + 1, ':'); + if (! tmp) + strncat(time, ":00", sizeof(time) - strlen(time) - 1); + + if (! strptime(time, "%H:%M:%S", &tm)) { + char errmsg[1024]; + snprintf(errmsg, sizeof(errmsg), + "Failed to parse '%s' as time", yytext); + sdb_parser_yyerror(yylloc, yyscanner, errmsg); + return SCANNER_ERROR; + } + + t = tm.tm_sec + 60 * tm.tm_min + 3600 * tm.tm_hour; + yylval->datetime = SECS_TO_SDB_TIME(t); + yylval->datetime += (sdb_time_t)strtoll(ns, NULL, 10); + return TIME; + } + += { return CMP_EQUAL; } +!= { return CMP_NEQUAL; } +=~ { return CMP_REGEX; } +!~ { return CMP_NREGEX; } +\< { return CMP_LT; } +\<= { return CMP_LE; } +\>= { return CMP_GE; } +\> { return CMP_GT; } +\|\| { return CONCAT; } + +. { /* XXX: */ return yytext[0]; } + +%% + +sdb_parser_yyscan_t +sdb_parser_scanner_init(const char *str, int len, sdb_parser_yyextra_t *yyext) +{ + yyscan_t scanner; + + if (! str) + return NULL; + + if (sdb_parser_yylex_init(&scanner)) { + char errbuf[1024]; + sdb_strbuf_sprintf(yyext->errbuf, "yylex_init_failed: %s", + sdb_strerror(errno, errbuf, sizeof(errbuf))); + return NULL; + } + + sdb_parser_yyset_extra(yyext, scanner); + + if (len < 0) + len = strlen(str); + + /* the newly allocated buffer state (YY_BUFFER_STATE) is stored inside the + * scanner and, thus, will be freed by yylex_destroy */ + sdb_parser_yy_scan_bytes(str, len, scanner); + return scanner; +} /* sdb_parser_scanner_init */ + +void +sdb_parser_scanner_destroy(sdb_parser_yyscan_t scanner) +{ + sdb_parser_yylex_destroy(scanner); +} /* sdb_parser_scanner_destroy */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/t/Makefile.am b/t/Makefile.am index 039995c..3f1fd4b 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -38,6 +38,7 @@ UNIT_TESTS = \ unit/frontend/parser_test \ unit/frontend/query_test \ unit/frontend/sock_test \ + unit/parser/parser_test \ unit/utils/avltree_test \ unit/utils/channel_test \ unit/utils/dbi_test \ @@ -101,6 +102,10 @@ unit_frontend_sock_test_SOURCES = $(UNIT_TEST_SOURCES) unit/frontend/sock_test.c unit_frontend_sock_test_CFLAGS = $(UNIT_TEST_CFLAGS) unit_frontend_sock_test_LDADD = $(UNIT_TEST_LDADD) +unit_parser_parser_test_SOURCES = $(UNIT_TEST_SOURCES) unit/parser/parser_test.c +unit_parser_parser_test_CFLAGS = $(UNIT_TEST_CFLAGS) +unit_parser_parser_test_LDADD = $(UNIT_TEST_LDADD) + unit_utils_avltree_test_SOURCES = $(UNIT_TEST_SOURCES) unit/utils/avltree_test.c unit_utils_avltree_test_CFLAGS = $(UNIT_TEST_CFLAGS) unit_utils_avltree_test_LDADD = $(UNIT_TEST_LDADD) diff --git a/t/unit/parser/parser_test.c b/t/unit/parser/parser_test.c new file mode 100644 index 0000000..c3aa09a --- /dev/null +++ b/t/unit/parser/parser_test.c @@ -0,0 +1,1016 @@ +/* + * SysDB - t/unit/parser/parser_test.c + * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include "parser/parser.h" +#include "core/object.h" +#include "testutils.h" + +#include +#include + +/* + * tests + */ + +struct { + const char *query; + int len; + int expected; + int expected_type; + int expected_extra; /* type-specific extra information */ +} parse_data[] = { + /* empty commands */ + { NULL, -1, -1, 0, 0 }, + { "", -1, 0, 0, 0 }, + { ";", -1, 0, 0, 0 }, + { ";;", -1, 0, 0, 0 }, + + /* FETCH commands */ + { "FETCH host 'host'", -1, 1, SDB_AST_TYPE_FETCH, SDB_HOST }, + { "FETCH host 'host' FILTER " + "age > 60s", -1, 1, SDB_AST_TYPE_FETCH, SDB_HOST }, + { "FETCH service " + "'host'.'service'", -1, 1, SDB_AST_TYPE_FETCH, SDB_SERVICE }, + { "FETCH metric " + "'host'.'metric'", -1, 1, SDB_AST_TYPE_FETCH, SDB_METRIC }, + + /* LIST commands */ + { "LIST hosts", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts -- foo", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts;", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts; INVALID", 11, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts FILTER " + "age > 60s", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST services", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST services FILTER " + "age > 60s", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST metrics", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + { "LIST metrics FILTER " + "age > 60s", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + /* field access */ + { "LIST hosts FILTER " + "name = 'a'", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts FILTER " + "last_update > 1s", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts FILTER " + "age > 120s", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts FILTER " + "interval > 10s", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts FILTER " + "backend = ['b']", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST hosts FILTER ANY " + "attribute.value = 'a'", -1, 1, SDB_AST_TYPE_LIST, SDB_HOST }, + { "LIST services FILTER " + "name = 'a'", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST services FILTER " + "last_update > 1s", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST services FILTER " + "age > 120s", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST services FILTER " + "interval > 10s", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST services FILTER " + "backend = ['b']", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST services FILTER ANY " + "attribute.value = 'a'", -1, 1, SDB_AST_TYPE_LIST, SDB_SERVICE }, + { "LIST metrics FILTER " + "name = 'a'", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + { "LIST metrics FILTER " + "last_update > 1s", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + { "LIST metrics FILTER " + "age > 120s", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + { "LIST metrics FILTER " + "interval > 10s", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + { "LIST metrics FILTER " + "backend = ['b']", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + { "LIST metrics FILTER ANY " + "attribute.value = 'a'", -1, 1, SDB_AST_TYPE_LIST, SDB_METRIC }, + + /* LOOKUP commands */ + { "LOOKUP hosts", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name = 'host'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING NOT " + "name = 'host'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name =~ 'p' AND " + "ANY service.name =~ 'p'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING NOT " + "name =~ 'p' AND " + "ANY service.name =~ 'p'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name =~ 'p' AND " + "ANY service.name =~ 'p' OR " + "ANY service.name =~ 'r'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING NOT " + "name =~ 'p' AND " + "ANY service.name =~ 'p' OR " + "ANY service.name =~ 'r'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name =~ 'p' " + "FILTER age > 1D", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name =~ 'p' " + "FILTER age > 1D AND " + "interval < 240s" , -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name =~ 'p' " + "FILTER NOT age>1D", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name =~ 'p' " + "FILTER age>" + "interval", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "host.name =~ 'p'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP services", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_SERVICE }, + { "LOOKUP services MATCHING ANY " + "attribute.name =~ 'a'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_SERVICE }, + { "LOOKUP services MATCHING " + "host.name = 'p'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_SERVICE }, + { "LOOKUP services MATCHING " + "service.name = 'p'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_SERVICE }, + { "LOOKUP metrics", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_METRIC }, + { "LOOKUP metrics MATCHING ANY " + "attribute.name =~ 'a'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_METRIC }, + { "LOOKUP metrics MATCHING " + "host.name = 'p'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_METRIC }, + { "LOOKUP metrics MATCHING " + "metric.name = 'p'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_METRIC }, + + /* TIMESERIES commands */ + { "TIMESERIES 'host'.'metric' " + "START 2014-01-01 " + "END 2014-12-31 " + "23:59:59", -1, 1, SDB_AST_TYPE_TIMESERIES, 0 }, + { "TIMESERIES 'host'.'metric' " + "START 2014-02-02 " + "14:02", -1, 1, SDB_AST_TYPE_TIMESERIES, 0 }, + { "TIMESERIES 'host'.'metric' " + "END 2014-02-02", -1, 1, SDB_AST_TYPE_TIMESERIES, 0 }, + { "TIMESERIES " + "'host'.'metric'", -1, 1, SDB_AST_TYPE_TIMESERIES, 0 }, + + /* STORE commands */ + { "STORE host 'host'", -1, 1, SDB_AST_TYPE_STORE, SDB_HOST }, + { "STORE host 'host' " + "LAST UPDATE " + "2015-02-01", -1, 1, SDB_AST_TYPE_STORE, SDB_HOST }, + { "STORE host attribute " + "'host'.'key' 123", -1, 1, SDB_AST_TYPE_STORE, SDB_ATTRIBUTE }, + { "STORE host attribute " + "'host'.'key' 123 " + "LAST UPDATE " + "2015-02-01", -1, 1, SDB_AST_TYPE_STORE, SDB_ATTRIBUTE }, + { "STORE service " + "'host'.'svc'", -1, 1, SDB_AST_TYPE_STORE, SDB_SERVICE }, + { "STORE service " + "'host'.'svc' " + "LAST UPDATE " + "2015-02-01", -1, 1, SDB_AST_TYPE_STORE, SDB_SERVICE }, + { "STORE service attribute " + "'host'.'svc'.'key' " + "123", -1, 1, SDB_AST_TYPE_STORE, SDB_ATTRIBUTE }, + { "STORE service attribute " + "'host'.'svc'.'key' " + "123 " + "LAST UPDATE " + "2015-02-01", -1, 1, SDB_AST_TYPE_STORE, SDB_ATTRIBUTE }, + { "STORE metric " + "'host'.'metric'", -1, 1, SDB_AST_TYPE_STORE, SDB_METRIC }, + { "STORE metric " + "'host'.'metric' " + "LAST UPDATE " + "2015-02-01", -1, 1, SDB_AST_TYPE_STORE, SDB_METRIC }, + { "STORE metric " + "'host'.'metric' " + "STORE 'typ' 'id' " + "LAST UPDATE " + "2015-02-01", -1, 1, SDB_AST_TYPE_STORE, SDB_METRIC }, + { "STORE metric attribute " + "'host'.'metric'.'key' " + "123", -1, 1, SDB_AST_TYPE_STORE, SDB_ATTRIBUTE }, + { "STORE metric attribute " + "'host'.'metric'.'key' " + "123 " + "LAST UPDATE " + "2015-02-01", -1, 1, SDB_AST_TYPE_STORE, SDB_ATTRIBUTE }, + + /* string constants */ + { "LOOKUP hosts MATCHING " + "name = ''''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name = '''foo'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name = 'f''oo'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name = 'foo'''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name = '''", -1, -1, 0, SDB_HOST }, + + /* numeric constants */ + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "1234", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] != " + "+234", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] < " + "-234", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] > " + "12.4", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] <= " + "12. + .3", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] <= " + "'f' || 'oo'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] >= " + ".4", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "+12e3", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "+12e-3", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "-12e+3", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + + /* date, time, interval constants */ + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "1 Y 42D", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "1s 42D", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + /* + * 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, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + */ + + /* array constants */ + { "LOOKUP hosts MATCHING " + "backend = ['foo']", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "backend = ['a','b']", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + + /* array iteration */ + { "LOOKUP hosts MATCHING " + "'foo' IN backend", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING 'foo' " + "NOT IN backend", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "['foo','bar'] " + "IN backend ", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + /* attribute type is unknown */ + { "LOOKUP hosts MATCHING " + "attribute['backend'] " + "IN backend ", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend < 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend <= 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend = 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend != 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend >= 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend > 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend =~ 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY backend !~ 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend < 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend <= 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend = 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend != 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend >= 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend > 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend =~ 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ALL backend !~ 'b'", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + /* attribute type is unknown */ + { "LOOKUP hosts MATCHING " + "ANY backend = attribute['backend']", + -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + + /* valid operand types */ + { "LOOKUP hosts MATCHING " + "age * 1 > 0s", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "age / 1 > 0s", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name > ''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name >= ''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name != ''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name = ''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name <= ''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "name < ''", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + + /* NULL */ + { "LOOKUP hosts MATCHING " + "attribute['foo'] " + "IS NULL", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] " + "IS NOT NULL", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "NOT attribute['foo'] " + "IS NULL", -1, 1, SDB_AST_TYPE_LOOKUP, SDB_HOST }, + { "LOOKUP hosts MATCHING " + "ANY service.name IS NULL", -1, -1, 0, 0 }, + + /* invalid numeric constants */ + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "+-12e+3", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "-12e-+3", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "e+3", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "3e", -1, -1, 0, 0 }, + /* following SQL standard, we don't support hex numbers */ + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "0x12", -1, -1, 0, 0 }, + + /* invalid expressions */ + { "LOOKUP hosts MATCHING " + "attr['foo'] = 1.23", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "attr['foo'] IS NULL", -1, -1, 0, 0 }, + + /* comments */ + { "/* some comment */", -1, 0, 0, 0 }, + { "-- another comment", -1, 0, 0, 0 }, + + /* syntax errors */ + { "INVALID", -1, -1, 0, 0 }, + { "FETCH host", -1, -1, 0, 0 }, + { "FETCH 'host'", -1, -1, 0, 0 }, + { "LIST hosts; INVALID", -1, -1, 0, 0 }, + { "/* some incomplete", -1, -1, 0, 0 }, + + /* + * syntactically correct but semantically invalid commands + */ + +#if 0 + /* invalid fields */ + { "LIST hosts FILTER " + "value = 'a'", -1, -1, 0, 0 }, + { "LIST services FILTER " + "value = 'a'", -1, -1, 0, 0 }, + { "LIST metrics FILTER " + "value = 'a'", -1, -1, 0, 0 }, + + /* type mismatches */ + { "LOOKUP hosts MATCHING " + "attribute['foo'] = " + "1.23 + 'foo'", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "1 IN backend ", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "1 NOT IN backend ", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "ANY backend !~ backend", + -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "ANY backend = 1", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age > 0", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "NOT age > 0", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age >= 0", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age = 0", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age != 0", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age <= 0", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age < 0", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age + 1 > 0s", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age - 1 > 0s", -1, -1, 0, 0 }, + /* datetime integer is allowed */ + { "LOOKUP hosts MATCHING " + "age || 1 > 0s", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name + 1 = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name - 1 = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name * 1 = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name / 1 = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name % 1 = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "(name % 1) + 1 = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "1 + (name % 1) = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "'' = 1 + (name % 1)", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age > 0 AND " + "age = 0s", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "age = 0s AND " + "age > 0", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "host.name > 0", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "backend > 'b'", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "'b' > backend", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "attribute['a'] > backend", + -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "backend > attribute['a']", + -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "host.name + 1 = ''", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "'a' + 1 IN 'b'", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "'a' IN 'b' - 1", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name + 1 IN 'b'", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "'a' IN name - 1", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "'b' IN 'abc'", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "1 IN age", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name =~ 'a' + 1", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name =~ name + 1", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name + 1 =~ 'a'", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name =~ 1", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "name + 1 IS NULL", -1, -1, 0, 0 }, + { "LOOKUP hosts FILTER " + "name + 1 IS NULL", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "ANY 'patt' =~ 'p'", -1, -1, 0, 0 }, + + /* invalid LIST commands */ + { "LIST", -1, -1, 0, 0 }, + { "LIST foo", -1, -1, 0, 0 }, + { "LIST hosts MATCHING " + "name = 'host'", -1, -1, 0, 0 }, + { "LIST foo FILTER " + "age > 60s", -1, -1, 0, 0 }, + + /* invalid FETCH commands */ + { "FETCH host 'host' MATCHING " + "name = 'host'", -1, -1, 0, 0 }, + { "FETCH service 'host'",-1, -1, 0, 0 }, + { "FETCH metric 'host'", -1, -1, 0, 0 }, + { "FETCH host " + "'host'.'localhost'", -1, -1, 0, 0 }, + { "FETCH foo 'host'", -1, -1, 0, 0 }, + { "FETCH foo 'host' FILTER " + "age > 60s", -1, -1, 0, 0 }, + + /* invalid LOOKUP commands */ + { "LOOKUP foo", -1, -1, 0, 0 }, + { "LOOKUP foo MATCHING " + "name = 'host'", -1, -1, 0, 0 }, + { "LOOKUP foo FILTER " + "age > 60s", -1, -1, 0, 0 }, + { "LOOKUP foo MATCHING " + "name = 'host' FILTER " + "age > 60s", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] <= " + "f || 'oo'", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "attribute['foo'] <= " + "'f' || oo", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "ANY host.name = 'host'", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "ANY service.name > 1", -1, -1, 0, 0 }, + { "LOOKUP hosts MATCHING " + "service.name = 's'", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "ANY host.name = 'host'", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "ANY service.name = 'svc'", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "ANY metric.name = 'm'", -1, -1, 0, 0 }, + { "LOOKUP services MATCHING " + "metric.name = 'm'", -1, -1, 0, 0 }, + { "LOOKUP metrics MATCHING " + "ANY host.name = 'host'", -1, -1, 0, 0 }, + { "LOOKUP metrics MATCHING " + "ANY service.name = 'svc'", -1, -1, 0, 0 }, + { "LOOKUP metrics MATCHING " + "ANY metric.name = 'm'", -1, -1, 0, 0 }, + { "LOOKUP metrics MATCHING " + "service.name = 'm'", -1, -1, 0, 0 }, + + /* invalid STORE commands */ + { "STORE host " + "'obj'.'host'", -1, -1, 0, 0 }, + { "STORE host attribute " + ".'key' 123", -1, -1, 0, 0 }, + { "STORE host attribute " + "'o'.'h'.'key' 123", -1, -1, 0, 0 }, + { "STORE service 'svc'", -1, -1, 0, 0 }, + { "STORE service " + "'host'.'svc' " + "STORE 'typ' 'id' " + "LAST UPDATE " + "2015-02-01", -1, -1, 0, 0 }, + { "STORE service attribute " + "'svc'.'key' 123", -1, -1, 0, 0 }, + { "STORE metric 'm'", -1, -1, 0, 0 }, + { "STORE metric " + "'host'.'metric' " + "STORE 'typ'.'id' " + "LAST UPDATE " + "2015-02-01", -1, -1, 0, 0 }, + { "STORE metric attribute " + "'metric'.'key' 123", -1, -1, 0, 0 }, +#endif +}; + +START_TEST(test_parse) +{ + sdb_strbuf_t *errbuf = sdb_strbuf_create(64); + sdb_llist_t *check; + sdb_ast_node_t *node; + _Bool ok; + + check = sdb_parser_parse(parse_data[_i].query, + parse_data[_i].len, errbuf); + if (parse_data[_i].expected < 0) + ok = check == 0; + else + ok = sdb_llist_len(check) == (size_t)parse_data[_i].expected; + + fail_unless(ok, "sdb_parser_parse(%s) = %p (len: %zu); expected: %d " + "(parse error: %s)", parse_data[_i].query, check, + sdb_llist_len(check), parse_data[_i].expected, + sdb_strbuf_string(errbuf)); + + if (! check) { + sdb_strbuf_destroy(errbuf); + return; + } + + if ((! parse_data[_i].expected_type) + || (parse_data[_i].expected <= 0)) { + sdb_llist_destroy(check); + sdb_strbuf_destroy(errbuf); + return; + } + + node = SDB_AST_NODE(sdb_llist_get(check, 0)); + fail_unless(node->type == parse_data[_i].expected_type, + "sdb_parser_parse(%s)->type = %i; expected: %d", + parse_data[_i].query, node->type, + parse_data[_i].expected_type); + + if (node->type == SDB_AST_TYPE_FETCH) { + sdb_ast_fetch_t *f = SDB_AST_FETCH(node); + fail_unless(f->obj_type == parse_data[_i].expected_extra, + "sdb_parser_parse(%s)->obj_type = %s; expected: %s", + parse_data[_i].query, SDB_STORE_TYPE_TO_NAME(f->obj_type), + SDB_STORE_TYPE_TO_NAME(parse_data[_i].expected_extra)); + } + else if (node->type == SDB_AST_TYPE_LIST) { + sdb_ast_list_t *l = SDB_AST_LIST(node); + fail_unless(l->obj_type == parse_data[_i].expected_extra, + "sdb_parser_parse(%s)->obj_type = %s; expected: %s", + parse_data[_i].query, SDB_STORE_TYPE_TO_NAME(l->obj_type), + SDB_STORE_TYPE_TO_NAME(parse_data[_i].expected_extra)); + } + else if (node->type == SDB_AST_TYPE_LOOKUP) { + sdb_ast_lookup_t *l = SDB_AST_LOOKUP(node); + fail_unless(l->obj_type == parse_data[_i].expected_extra, + "sdb_parser_parse(%s)->obj_type = %s; expected: %s", + parse_data[_i].query, SDB_STORE_TYPE_TO_NAME(l->obj_type), + SDB_STORE_TYPE_TO_NAME(parse_data[_i].expected_extra)); + } + else if (node->type == SDB_AST_TYPE_STORE) { + sdb_ast_store_t *s = SDB_AST_STORE(node); + fail_unless(s->obj_type == parse_data[_i].expected_extra, + "sdb_parser_parse(%s)->obj_type = %s; expected: %s", + parse_data[_i].query, SDB_STORE_TYPE_TO_NAME(s->obj_type), + SDB_STORE_TYPE_TO_NAME(parse_data[_i].expected_extra)); + } + + sdb_object_deref(SDB_OBJ(node)); + sdb_llist_destroy(check); + sdb_strbuf_destroy(errbuf); +} +END_TEST + +struct { + const char *expr; + int len; + int expected; +} parse_conditional_data[] = { + /* empty expressions */ + { NULL, -1, -1 }, + { "", -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' ", 13, SDB_AST_EQ }, + { "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 }, + /* 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 }, + /* 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 }, + /* 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 }, + /* composite expressions */ + { "name =~ 'pattern' AND " + "ANY service.name =~ 'pattern'", -1, SDB_AST_AND }, + { "name =~ 'pattern' OR " + "ANY service.name =~ 'pattern'", -1, SDB_AST_OR }, + { "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 }, + /* datetime expressions */ + { "attribute['foo'] = " + "2014-08-16", -1, SDB_AST_EQ }, + { "attribute['foo'] = " + "17:23", -1, SDB_AST_EQ }, + { "attribute['foo'] = " + "17:23:53", -1, SDB_AST_EQ }, + { "attribute['foo'] = " + "17:23:53.123", -1, SDB_AST_EQ }, + { "attribute['foo'] = " + "17:23:53.123456789", -1, SDB_AST_EQ }, + { "attribute['foo'] = " + "2014-08-16 17:23", -1, SDB_AST_EQ }, + { "attribute['foo'] = " + "2014-08-16 17:23:53", -1, SDB_AST_EQ }, + /* NULL */ + { "attribute['foo'] IS NULL", -1, SDB_AST_ISNULL }, + { "attribute['foo'] IS NOT NULL", -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 }, + + /* 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 }, + + /* check operator precedence */ + { "name = 'name' OR " + "ANY service.name = 'name' AND " + "ANY attribute.name = 'name' OR " + "attribute['foo'] = 'bar'", -1, SDB_AST_OR }, + { "name = 'name' AND " + "ANY service.name = 'name' AND " + "ANY attribute.name = 'name' OR " + "attribute['foo'] = 'bar'", -1, SDB_AST_OR }, + { "name = 'name' AND " + "ANY service.name = 'name' OR " + "ANY attribute.name = 'name' AND " + "attribute['foo'] = 'bar'", -1, SDB_AST_OR }, + { "(name = 'name' OR " + "ANY service.name = 'name') AND " + "(ANY attribute.name = 'name' OR " + "attribute['foo'] = 'bar')", -1, SDB_AST_AND }, + { "NOT name = 'name' OR " + "ANY service.name = 'name'", -1, SDB_AST_OR }, + { "NOT name = 'name' OR " + "NOT ANY service.name = 'name'", -1, SDB_AST_OR }, + { "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 }, +}; + +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); + + if (parse_conditional_data[_i].expected < 0) { + fail_unless(node == NULL, + "sdb_parser_parse_conditional(%s) = %p; expected: NULL", + 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; " + "expected: (parse error: %s)", + 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_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_AST_ITER(node)->kind, parse_conditional_data[_i].expected); + + sdb_object_deref(SDB_OBJ(node)); + sdb_strbuf_destroy(errbuf); +} +END_TEST + +struct { + const char *expr; + int len; + int expected; +} parse_arith_data[] = { + /* empty expressions */ + { NULL, -1, -1 }, + { "", -1, -1 }, + + /* constant expressions */ + { "'localhost'", -1, SDB_AST_TYPE_CONST }, + { "123", -1, SDB_AST_TYPE_CONST }, + { "2014-08-16", -1, SDB_AST_TYPE_CONST }, + { "17:23", -1, SDB_AST_TYPE_CONST }, + { "17:23:53", -1, SDB_AST_TYPE_CONST }, + { "17:23:53.123", -1, SDB_AST_TYPE_CONST }, + { "17:23:53.123456789", -1, SDB_AST_TYPE_CONST }, + { "2014-08-16 17:23", -1, SDB_AST_TYPE_CONST }, + { "2014-08-16 17:23:53", -1, SDB_AST_TYPE_CONST }, + { "10s", -1, SDB_AST_TYPE_CONST }, + { "60m", -1, SDB_AST_TYPE_CONST }, + { "10Y 24D 1h", -1, SDB_AST_TYPE_CONST }, + + /* TODO: the analyzer and/or optimizer should turn these into constants */ + { "123 + 456", -1, SDB_AST_ADD }, + { "'foo' || 'bar'", -1, SDB_AST_CONCAT }, + { "456 - 123", -1, SDB_AST_SUB }, + { "1.2 * 3.4", -1, SDB_AST_MUL }, + { "1.2 / 3.4", -1, SDB_AST_DIV }, + { "5 % 2", -1, SDB_AST_MOD }, + + /* queryable fields */ + { "last_update", -1, SDB_AST_TYPE_VALUE }, + { "AGE", -1, SDB_AST_TYPE_VALUE }, + { "interval", -1, SDB_AST_TYPE_VALUE }, + { "Last_Update", -1, SDB_AST_TYPE_VALUE }, + { "backend", -1, SDB_AST_TYPE_VALUE }, + + /* attributes */ + { "attribute['foo']", -1, SDB_AST_TYPE_VALUE }, + + /* arithmetic expressions */ + { "age + age", -1, SDB_AST_ADD }, + { "age - age", -1, SDB_AST_SUB }, + { "age * age", -1, SDB_AST_MUL }, + { "age / age", -1, SDB_AST_DIV }, + { "age \% age", -1, SDB_AST_MOD }, + { "age || age", -1, SDB_AST_CONCAT }, + + /* operator precedence */ + { "age + age * age", -1, SDB_AST_ADD }, + { "age * age + age", -1, SDB_AST_ADD }, + { "age + age - age", -1, SDB_AST_SUB }, + { "age - age + age", -1, SDB_AST_ADD }, + { "(age + age) * age", -1, SDB_AST_MUL }, + { "age + (age * age)", -1, SDB_AST_ADD }, + + /* syntax errors */ + { "LIST", -1, -1 }, + { "foo &^ bar", -1, -1 }, + { "invalid", -1, -1 }, +}; + +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); + + if (parse_arith_data[_i].expected < 0) { + fail_unless(node == NULL, + "sdb_parser_parse_arith(%s) = %p; expected: NULL", + 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; " + "expected: (parse error: %s)", + 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); + 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, + parse_arith_data[_i].expected); + + sdb_object_deref(SDB_OBJ(node)); + sdb_strbuf_destroy(errbuf); +} +END_TEST + +TEST_MAIN("parser::parser") +{ + TCase *tc = tcase_create("core"); + TC_ADD_LOOP_TEST(tc, parse); + TC_ADD_LOOP_TEST(tc, parse_conditional); + TC_ADD_LOOP_TEST(tc, parse_arith); + ADD_TCASE(tc); +} +TEST_MAIN_END + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/t/unit/testutils.h b/t/unit/testutils.h index 3bdf0a1..a698965 100644 --- a/t/unit/testutils.h +++ b/t/unit/testutils.h @@ -46,6 +46,7 @@ int failed; \ putenv("TZ=UTC"); \ s = suite_create(name); \ + sr = srunner_create(s); \ #define TC_ADD_LOOP_TEST(tc, name) \ tcase_add_loop_test((tc), test_ ## name, \ @@ -54,7 +55,6 @@ #define ADD_TCASE(tc) suite_add_tcase(s, (tc)) #define TEST_MAIN_END \ - sr = srunner_create(s); \ srunner_run_all(sr, CK_NORMAL); \ failed = srunner_ntests_failed(sr); \ srunner_free(sr); \