1 /*
2 * SysDB - src/frontend/grammar.y
3 * Copyright (C) 2013 Sebastian 'tokkee' Harl <sh@tokkee.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
28 %{
30 #include "frontend/connection-private.h"
31 #include "frontend/parser.h"
32 #include "frontend/grammar.h"
34 #include "core/store.h"
35 #include "core/store-private.h"
36 #include "core/time.h"
38 #include "utils/error.h"
39 #include "utils/llist.h"
41 #include <assert.h>
43 #include <stdio.h>
44 #include <string.h>
46 /*
47 * private helper functions
48 */
50 static sdb_store_matcher_t *
51 name_matcher(const char *type_name, const char *cmp, sdb_store_expr_t *expr);
53 static sdb_store_matcher_t *
54 name_iter_matcher(int m_type, const char *type_name, const char *cmp,
55 sdb_store_expr_t *expr);
57 /*
58 * public API
59 */
61 int
62 sdb_fe_yylex(YYSTYPE *yylval, YYLTYPE *yylloc, sdb_fe_yyscan_t yyscanner);
64 sdb_fe_yyextra_t *
65 sdb_fe_yyget_extra(sdb_fe_yyscan_t scanner);
67 void
68 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
70 /* quick access to the current parse tree */
71 #define pt sdb_fe_yyget_extra(scanner)->parsetree
73 /* quick access to the parser mode */
74 #define parser_mode sdb_fe_yyget_extra(scanner)->mode
76 #define MODE_TO_STRING(m) \
77 (((m) == SDB_PARSE_DEFAULT) ? "statement" \
78 : ((m) == SDB_PARSE_COND) ? "condition" \
79 : ((m) == SDB_PARSE_EXPR) ? "expression" \
80 : "UNKNOWN")
82 %}
84 %pure-parser
85 %lex-param {sdb_fe_yyscan_t scanner}
86 %parse-param {sdb_fe_yyscan_t scanner}
87 %locations
88 %error-verbose
89 %expect 0
90 %name-prefix "sdb_fe_yy"
92 %union {
93 const char *sstr; /* static string */
94 char *str;
96 sdb_data_t data;
97 sdb_time_t datetime;
99 sdb_llist_t *list;
100 sdb_conn_node_t *node;
102 sdb_store_matcher_t *m;
103 sdb_store_expr_t *expr;
104 }
106 %start statements
108 %token SCANNER_ERROR
110 %token AND OR IS NOT MATCHING FILTER
111 %token CMP_EQUAL CMP_NEQUAL CMP_REGEX CMP_NREGEX
112 %token CMP_LT CMP_LE CMP_GE CMP_GT ALL ANY IN
113 %token CONCAT
115 %token START END
117 /* NULL token */
118 %token NULL_T
120 %token FETCH LIST LOOKUP TIMESERIES
122 %token <str> IDENTIFIER STRING
124 %token <data> INTEGER FLOAT
126 %token <datetime> DATE TIME
128 /* Precedence (lowest first): */
129 %left OR
130 %left AND
131 %right NOT
132 %left CMP_EQUAL CMP_NEQUAL
133 %left CMP_LT CMP_LE CMP_GE CMP_GT
134 %nonassoc CMP_REGEX CMP_NREGEX
135 %nonassoc IN
136 %left CONCAT
137 %nonassoc IS
138 %left '+' '-'
139 %left '*' '/' '%'
140 %left '[' ']'
141 %left '(' ')'
142 %left '.'
144 %type <list> statements
145 %type <node> statement
146 fetch_statement
147 list_statement
148 lookup_statement
149 timeseries_statement
150 matching_clause
151 filter_clause
152 condition
154 %type <m> matcher
155 compare_matcher
157 %type <expr> expression
159 %type <sstr> cmp
161 %type <data> data
162 interval interval_elem
164 %type <datetime> datetime
165 start_clause end_clause
167 %destructor { free($$); } <str>
168 %destructor { sdb_object_deref(SDB_OBJ($$)); } <node> <m> <expr>
169 %destructor { sdb_data_free_datum(&$$); } <data>
171 %%
173 statements:
174 statements ';' statement
175 {
176 /* only accepted in default parse mode */
177 if (parser_mode != SDB_PARSE_DEFAULT) {
178 char errmsg[1024];
179 snprintf(errmsg, sizeof(errmsg),
180 YY_("syntax error, unexpected statement, "
181 "expecting %s"), MODE_TO_STRING(parser_mode));
182 sdb_fe_yyerror(&yylloc, scanner, errmsg);
183 sdb_object_deref(SDB_OBJ($3));
184 YYABORT;
185 }
187 if ($3) {
188 sdb_llist_append(pt, SDB_OBJ($3));
189 sdb_object_deref(SDB_OBJ($3));
190 }
191 }
192 |
193 statement
194 {
195 /* only accepted in default parse mode */
196 if (parser_mode != SDB_PARSE_DEFAULT) {
197 char errmsg[1024];
198 snprintf(errmsg, sizeof(errmsg),
199 YY_("syntax error, unexpected statement, "
200 "expecting %s"), MODE_TO_STRING(parser_mode));
201 sdb_fe_yyerror(&yylloc, scanner, errmsg);
202 sdb_object_deref(SDB_OBJ($1));
203 YYABORT;
204 }
206 if ($1) {
207 sdb_llist_append(pt, SDB_OBJ($1));
208 sdb_object_deref(SDB_OBJ($1));
209 }
210 }
211 |
212 condition
213 {
214 /* only accepted in condition parse mode */
215 if (! (parser_mode & SDB_PARSE_COND)) {
216 char errmsg[1024];
217 snprintf(errmsg, sizeof(errmsg),
218 YY_("syntax error, unexpected condition, "
219 "expecting %s"), MODE_TO_STRING(parser_mode));
220 sdb_fe_yyerror(&yylloc, scanner, errmsg);
221 sdb_object_deref(SDB_OBJ($1));
222 YYABORT;
223 }
225 if ($1) {
226 sdb_llist_append(pt, SDB_OBJ($1));
227 sdb_object_deref(SDB_OBJ($1));
228 }
229 }
230 |
231 expression
232 {
233 /* only accepted in expression parse mode */
234 if (! (parser_mode & SDB_PARSE_EXPR)) {
235 char errmsg[1024];
236 snprintf(errmsg, sizeof(errmsg),
237 YY_("syntax error, unexpected expression, "
238 "expecting %s"), MODE_TO_STRING(parser_mode));
239 sdb_fe_yyerror(&yylloc, scanner, errmsg);
240 sdb_object_deref(SDB_OBJ($1));
241 YYABORT;
242 }
244 if ($1) {
245 sdb_conn_node_t *n;
246 n = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
247 conn_expr_t, conn_expr_destroy));
248 n->cmd = CONNECTION_EXPR;
249 CONN_EXPR(n)->expr = $1;
251 sdb_llist_append(pt, SDB_OBJ(n));
252 sdb_object_deref(SDB_OBJ(n));
253 }
254 }
255 ;
257 statement:
258 fetch_statement
259 |
260 list_statement
261 |
262 lookup_statement
263 |
264 timeseries_statement
265 |
266 /* empty */
267 {
268 $$ = NULL;
269 }
270 ;
272 /*
273 * FETCH <type> <hostname> [FILTER <condition>];
274 *
275 * Retrieve detailed information about a single host.
276 */
277 fetch_statement:
278 FETCH IDENTIFIER STRING filter_clause
279 {
280 /* TODO: support other types as well */
281 if (strcasecmp($2, "host")) {
282 char errmsg[strlen($2) + 32];
283 snprintf(errmsg, sizeof(errmsg),
284 YY_("unknown data-source %s"), $2);
285 sdb_fe_yyerror(&yylloc, scanner, errmsg);
286 free($2); $2 = NULL;
287 free($3); $3 = NULL;
288 sdb_object_deref(SDB_OBJ($4));
289 YYABORT;
290 }
292 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
293 conn_fetch_t, conn_fetch_destroy));
294 CONN_FETCH($$)->type = SDB_HOST;
295 CONN_FETCH($$)->name = $3;
296 CONN_FETCH($$)->filter = CONN_MATCHER($4);
297 $$->cmd = CONNECTION_FETCH;
298 free($2); $2 = NULL;
299 }
300 ;
302 /*
303 * LIST <type> [FILTER <condition>];
304 *
305 * Returns a list of all hosts in the store.
306 */
307 list_statement:
308 LIST IDENTIFIER filter_clause
309 {
310 int type = sdb_store_parse_object_type_plural($2);
311 if ((type < 0) || (type == SDB_ATTRIBUTE)) {
312 char errmsg[strlen($2) + 32];
313 snprintf(errmsg, sizeof(errmsg),
314 YY_("unknown data-source %s"), $2);
315 sdb_fe_yyerror(&yylloc, scanner, errmsg);
316 free($2); $2 = NULL;
317 sdb_object_deref(SDB_OBJ($3));
318 YYABORT;
319 }
321 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
322 conn_list_t, conn_list_destroy));
323 CONN_LIST($$)->type = type;
324 CONN_LIST($$)->filter = CONN_MATCHER($3);
325 $$->cmd = CONNECTION_LIST;
326 free($2); $2 = NULL;
327 }
328 ;
330 /*
331 * LOOKUP <type> MATCHING <condition> [FILTER <condition>];
332 *
333 * Returns detailed information about <type> matching condition.
334 */
335 lookup_statement:
336 LOOKUP IDENTIFIER matching_clause filter_clause
337 {
338 /* TODO: support other types as well */
339 if (strcasecmp($2, "hosts")) {
340 char errmsg[strlen($2) + 32];
341 snprintf(errmsg, sizeof(errmsg),
342 YY_("unknown data-source %s"), $2);
343 sdb_fe_yyerror(&yylloc, scanner, errmsg);
344 free($2); $2 = NULL;
345 sdb_object_deref(SDB_OBJ($3));
346 sdb_object_deref(SDB_OBJ($4));
347 YYABORT;
348 }
350 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
351 conn_lookup_t, conn_lookup_destroy));
352 CONN_LOOKUP($$)->type = SDB_HOST;
353 CONN_LOOKUP($$)->matcher = CONN_MATCHER($3);
354 CONN_LOOKUP($$)->filter = CONN_MATCHER($4);
355 $$->cmd = CONNECTION_LOOKUP;
356 free($2); $2 = NULL;
357 }
358 ;
360 matching_clause:
361 MATCHING condition { $$ = $2; }
362 |
363 /* empty */ { $$ = NULL; }
365 filter_clause:
366 FILTER condition { $$ = $2; }
367 |
368 /* empty */ { $$ = NULL; }
370 /*
371 * TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>];
372 *
373 * Returns a time-series for the specified host's metric.
374 */
375 timeseries_statement:
376 TIMESERIES STRING '.' STRING start_clause end_clause
377 {
378 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
379 conn_ts_t, conn_ts_destroy));
380 CONN_TS($$)->hostname = $2;
381 CONN_TS($$)->metric = $4;
382 CONN_TS($$)->opts.start = $5;
383 CONN_TS($$)->opts.end = $6;
384 $$->cmd = CONNECTION_TIMESERIES;
385 }
386 ;
388 start_clause:
389 START datetime { $$ = $2; }
390 |
391 /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; }
393 end_clause:
394 END datetime { $$ = $2; }
395 |
396 /* empty */ { $$ = sdb_gettime(); }
398 /*
399 * Basic expressions.
400 */
402 condition:
403 matcher
404 {
405 if (! $1) {
406 /* TODO: improve error reporting */
407 sdb_fe_yyerror(&yylloc, scanner,
408 YY_("syntax error, invalid condition"));
409 YYABORT;
410 }
412 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
413 conn_matcher_t, conn_matcher_destroy));
414 $$->cmd = CONNECTION_MATCHER;
415 CONN_MATCHER($$)->matcher = $1;
416 }
417 ;
419 matcher:
420 '(' matcher ')'
421 {
422 $$ = $2;
423 }
424 |
425 matcher AND matcher
426 {
427 $$ = sdb_store_con_matcher($1, $3);
428 sdb_object_deref(SDB_OBJ($1));
429 sdb_object_deref(SDB_OBJ($3));
430 }
431 |
432 matcher OR matcher
433 {
434 $$ = sdb_store_dis_matcher($1, $3);
435 sdb_object_deref(SDB_OBJ($1));
436 sdb_object_deref(SDB_OBJ($3));
437 }
438 |
439 NOT matcher
440 {
441 $$ = sdb_store_inv_matcher($2);
442 sdb_object_deref(SDB_OBJ($2));
443 }
444 |
445 compare_matcher
446 {
447 $$ = $1;
448 }
449 ;
451 compare_matcher:
452 expression cmp expression
453 {
454 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op($2);
455 assert(cb); /* else, the grammar accepts invalid 'cmp' */
456 $$ = cb($1, $3);
457 sdb_object_deref(SDB_OBJ($1));
458 sdb_object_deref(SDB_OBJ($3));
459 }
460 |
461 IDENTIFIER cmp expression
462 {
463 $$ = name_matcher($1, $2, $3);
464 free($1); $1 = NULL;
465 sdb_object_deref(SDB_OBJ($3));
466 }
467 |
468 ANY IDENTIFIER cmp expression
469 {
470 $$ = name_iter_matcher(MATCHER_ANY, $2, $3, $4);
471 free($2); $2 = NULL;
472 sdb_object_deref(SDB_OBJ($4));
473 }
474 |
475 ALL IDENTIFIER cmp expression
476 {
477 $$ = name_iter_matcher(MATCHER_ALL, $2, $3, $4);
478 free($2); $2 = NULL;
479 sdb_object_deref(SDB_OBJ($4));
480 }
481 |
482 expression IS NULL_T
483 {
484 $$ = sdb_store_isnull_matcher($1);
485 sdb_object_deref(SDB_OBJ($1));
486 }
487 |
488 expression IS NOT NULL_T
489 {
490 $$ = sdb_store_isnnull_matcher($1);
491 sdb_object_deref(SDB_OBJ($1));
492 }
493 |
494 expression IN expression
495 {
496 $$ = sdb_store_in_matcher($1, $3);
497 sdb_object_deref(SDB_OBJ($1));
498 sdb_object_deref(SDB_OBJ($3));
499 }
500 ;
502 expression:
503 '(' expression ')'
504 {
505 $$ = $2;
506 }
507 |
508 expression '+' expression
509 {
510 $$ = sdb_store_expr_create(SDB_DATA_ADD, $1, $3);
511 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
512 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
513 }
514 |
515 expression '-' expression
516 {
517 $$ = sdb_store_expr_create(SDB_DATA_SUB, $1, $3);
518 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
519 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
520 }
521 |
522 expression '*' expression
523 {
524 $$ = sdb_store_expr_create(SDB_DATA_MUL, $1, $3);
525 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
526 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
527 }
528 |
529 expression '/' expression
530 {
531 $$ = sdb_store_expr_create(SDB_DATA_DIV, $1, $3);
532 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
533 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
534 }
535 |
536 expression '%' expression
537 {
538 $$ = sdb_store_expr_create(SDB_DATA_MOD, $1, $3);
539 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
540 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
541 }
542 |
543 expression CONCAT expression
544 {
545 $$ = sdb_store_expr_create(SDB_DATA_CONCAT, $1, $3);
546 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
547 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
548 }
549 |
550 '.' IDENTIFIER
551 {
552 int field = sdb_store_parse_field_name($2);
553 free($2); $2 = NULL;
554 $$ = sdb_store_expr_fieldvalue(field);
555 }
556 |
557 IDENTIFIER '[' STRING ']'
558 {
559 if (strcasecmp($1, "attribute")) {
560 char errmsg[strlen($1) + strlen($3) + 32];
561 snprintf(errmsg, sizeof(errmsg),
562 YY_("unknown value %s[%s]"), $1, $3);
563 sdb_fe_yyerror(&yylloc, scanner, errmsg);
564 free($1); $1 = NULL;
565 free($3); $3 = NULL;
566 YYABORT;
567 }
568 $$ = sdb_store_expr_attrvalue($3);
569 free($1); $1 = NULL;
570 free($3); $3 = NULL;
571 }
572 |
573 data
574 {
575 $$ = sdb_store_expr_constvalue(&$1);
576 sdb_data_free_datum(&$1);
577 }
578 ;
580 cmp:
581 CMP_EQUAL { $$ = "="; }
582 |
583 CMP_NEQUAL { $$ = "!="; }
584 |
585 CMP_REGEX { $$ = "=~"; }
586 |
587 CMP_NREGEX { $$ = "!~"; }
588 |
589 CMP_LT { $$ = "<"; }
590 |
591 CMP_LE { $$ = "<="; }
592 |
593 CMP_GE { $$ = ">="; }
594 |
595 CMP_GT { $$ = ">"; }
596 ;
598 data:
599 STRING { $$.type = SDB_TYPE_STRING; $$.data.string = $1; }
600 |
601 INTEGER { $$ = $1; }
602 |
603 FLOAT { $$ = $1; }
604 |
605 datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; }
606 |
607 interval { $$ = $1; }
608 ;
610 datetime:
611 DATE TIME { $$ = $1 + $2; }
612 |
613 DATE { $$ = $1; }
614 |
615 TIME { $$ = $1; }
616 ;
618 interval:
619 interval interval_elem
620 {
621 $$.data.datetime = $1.data.datetime + $2.data.datetime;
622 }
623 |
624 interval_elem { $$ = $1; }
625 ;
627 interval_elem:
628 INTEGER IDENTIFIER
629 {
630 sdb_time_t unit = 1;
632 unit = sdb_strpunit($2);
633 if (! unit) {
634 char errmsg[strlen($2) + 32];
635 snprintf(errmsg, sizeof(errmsg),
636 YY_("invalid time unit %s"), $2);
637 sdb_fe_yyerror(&yylloc, scanner, errmsg);
638 free($2); $2 = NULL;
639 YYABORT;
640 }
641 free($2); $2 = NULL;
643 $$.type = SDB_TYPE_DATETIME;
644 $$.data.datetime = (sdb_time_t)$1.data.integer * unit;
646 if ($1.data.integer < 0) {
647 sdb_fe_yyerror(&yylloc, scanner,
648 YY_("syntax error, negative intervals not supported"));
649 YYABORT;
650 }
651 }
652 ;
654 %%
656 void
657 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg)
658 {
659 sdb_log(SDB_LOG_ERR, "frontend: parse error: %s", msg);
660 } /* sdb_fe_yyerror */
662 static sdb_store_matcher_t *
663 name_matcher(const char *type_name, const char *cmp, sdb_store_expr_t *expr)
664 {
665 int type = sdb_store_parse_object_type(type_name);
666 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op(cmp);
667 sdb_store_expr_t *e;
668 sdb_store_matcher_t *m;
669 assert(cb);
671 /* TODO: this only works as long as queries
672 * are limited to hosts */
673 if (type != SDB_HOST)
674 return NULL;
676 e = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
677 m = cb(e, expr);
678 sdb_object_deref(SDB_OBJ(e));
679 return m;
680 } /* name_matcher */
682 static sdb_store_matcher_t *
683 name_iter_matcher(int m_type, const char *type_name, const char *cmp,
684 sdb_store_expr_t *expr)
685 {
686 int type = sdb_store_parse_object_type(type_name);
687 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op(cmp);
688 sdb_store_expr_t *e;
689 sdb_store_matcher_t *m, *tmp = NULL;
690 assert(cb);
692 /* TODO: this only works as long as queries
693 * are limited to hosts */
694 if (type == SDB_HOST) {
695 return NULL;
696 }
698 e = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
699 m = cb(e, expr);
700 if (m_type == MATCHER_ANY)
701 tmp = sdb_store_any_matcher(type, m);
702 else if (m_type == MATCHER_ALL)
703 tmp = sdb_store_all_matcher(type, m);
704 sdb_object_deref(SDB_OBJ(m));
705 sdb_object_deref(SDB_OBJ(e));
706 return tmp;
707 } /* name_iter_matcher */
709 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */