summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: c1ebaba)
raw | patch | inline | side by side (parent: c1ebaba)
author | Sebastian Harl <sh@tokkee.org> | |
Sun, 12 Apr 2015 13:12:45 +0000 (15:12 +0200) | ||
committer | Sebastian Harl <sh@tokkee.org> | |
Sun, 12 Apr 2015 13:12:45 +0000 (15:12 +0200) |
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.
operator. The old parser is still in place unmodified and currently still in
use.
.gitignore | patch | blob | history | |
src/Makefile.am | patch | blob | history | |
src/include/parser/parser.h | [new file with mode: 0644] | patch | blob |
src/parser/grammar.y | [new file with mode: 0644] | patch | blob |
src/parser/parser.c | [new file with mode: 0644] | patch | blob |
src/parser/scanner.l | [new file with mode: 0644] | patch | blob |
t/Makefile.am | patch | blob | history | |
t/unit/parser/parser_test.c | [new file with mode: 0644] | patch | blob |
t/unit/testutils.h | patch | blob | history |
diff --git a/.gitignore b/.gitignore
index 002520ff88266eaecdb23d07b474e9a5deb3ac32..aa77a2cfabc68cc916ee5641bd47d7c2d2657ab8 100644 (file)
--- a/.gitignore
+++ b/.gitignore
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 a7f6c9b73744c4f27dade47960386a7bea35a110..5a3146af6f15a8037ddccd076ef006517f753356 100644 (file)
--- a/src/Makefile.am
+++ b/src/Makefile.am
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
# 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 = \
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
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * SysDB - src/include/parser/parser.h
+ * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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
--- /dev/null
+++ b/src/parser/grammar.y
@@ -0,0 +1,752 @@
+/*
+ * SysDB - src/parser/grammar.y
+ * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 <assert.h>
+
+#include <stdio.h>
+#include <string.h>
+
+/*
+ * 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 <str> IDENTIFIER STRING
+
+%token <data> INTEGER FLOAT
+
+%token <datetime> 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 <list> statements
+%type <node> statement
+ fetch_statement
+ list_statement
+ lookup_statement
+ store_statement
+ timeseries_statement
+ matching_clause
+ filter_clause
+ condition comparison
+ expression object_expression
+
+%type <integer> object_type object_type_plural
+%type <integer> field
+%type <integer> cmp
+
+%type <data> data
+ interval interval_elem
+ array array_elem_list
+
+%type <datetime> datetime
+ start_clause end_clause
+ last_update_clause
+
+%type <metric_store> metric_store_clause
+
+%destructor { free($$); } <str>
+%destructor { sdb_object_deref(SDB_OBJ($$)); } <node>
+%destructor { sdb_data_free_datum(&$$); } <data>
+
+%%
+
+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 <hostname> [FILTER <condition>];
+ * FETCH <type> <hostname>.<name> [FILTER <condition>];
+ *
+ * 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 <type> [FILTER <condition>];
+ *
+ * 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 <type> [MATCHING <condition>] [FILTER <condition>];
+ *
+ * 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 <type> <name>|<host>.<name> [LAST UPDATE <datetime>];
+ * STORE METRIC <host>.<name> STORE <type> <id> [LAST UPDATE <datetime>];
+ * STORE <type> ATTRIBUTE <parent>.<key> <datum> [LAST UPDATE <datetime>];
+ *
+ * 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 <host>.<metric> [START <datetime>] [END <datetime>];
+ *
+ * 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
--- /dev/null
+++ b/src/parser/parser.c
@@ -0,0 +1,185 @@
+/*
+ * SysDB - src/parser/parser.c
+ * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 <assert.h>
+#include <string.h>
+
+/*
+ * 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
--- /dev/null
+++ b/src/parser/scanner.l
@@ -0,0 +1,308 @@
+/*
+ * SysDB - src/parser/scanner.l
+ * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 <assert.h>
+#include <errno.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <time.h>
+
+#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>{csc_inside} { /* ignore */ }
+<CSC>{csc_end} { BEGIN(INITIAL); }
+<CSC><<EOF>> {
+ 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 039995cdc3fde350004d564c143544a6410aec1e..3f1fd4bc20179d0ac8dc2f2b4da763d788b664ee 100644 (file)
--- a/t/Makefile.am
+++ b/t/Makefile.am
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
--- /dev/null
@@ -0,0 +1,1016 @@
+/*
+ * SysDB - t/unit/parser/parser_test.c
+ * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 <check.h>
+#include <limits.h>
+
+/*
+ * 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 <mul/div> 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' <garbage>", 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: <cond> (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: <expr> (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 3bdf0a1c98f9f4c9db9e0cb657674a1038c16e19..a6989655e1c5d481426cab9ed83e750bf31bc8f6 100644 (file)
--- a/t/unit/testutils.h
+++ b/t/unit/testutils.h
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, \
#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); \