4dc6cb22411ce756991d07c0a01cd078d6949abd
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_iter_matcher(int m_type, int type, const char *cmp,
52 sdb_store_expr_t *expr);
54 /*
55 * public API
56 */
58 int
59 sdb_fe_yylex(YYSTYPE *yylval, YYLTYPE *yylloc, sdb_fe_yyscan_t yyscanner);
61 sdb_fe_yyextra_t *
62 sdb_fe_yyget_extra(sdb_fe_yyscan_t scanner);
64 void
65 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
66 void
67 sdb_fe_yyerrorf(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *fmt, ...);
69 /* quick access to the current parse tree */
70 #define pt sdb_fe_yyget_extra(scanner)->parsetree
72 /* quick access to the parser mode */
73 #define parser_mode sdb_fe_yyget_extra(scanner)->mode
75 #define MODE_TO_STRING(m) \
76 (((m) == SDB_PARSE_DEFAULT) ? "statement" \
77 : ((m) == SDB_PARSE_COND) ? "condition" \
78 : ((m) == SDB_PARSE_EXPR) ? "expression" \
79 : "UNKNOWN")
81 %}
83 %pure-parser
84 %lex-param {sdb_fe_yyscan_t scanner}
85 %parse-param {sdb_fe_yyscan_t scanner}
86 %locations
87 %error-verbose
88 %expect 0
89 %name-prefix "sdb_fe_yy"
91 %union {
92 const char *sstr; /* static string */
93 char *str;
94 int integer;
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 HOST_T HOSTS_T SERVICE_T SERVICES_T METRIC_T METRICS_T
116 %token ATTRIBUTE_T ATTRIBUTES_T
117 %token NAME_T LAST_UPDATE_T AGE_T INTERVAL_T BACKEND_T
119 %token START END
121 /* NULL token */
122 %token NULL_T
124 %token FETCH LIST LOOKUP TIMESERIES
126 %token <str> IDENTIFIER STRING
128 %token <data> INTEGER FLOAT
130 %token <datetime> DATE TIME
132 /* Precedence (lowest first): */
133 %left OR
134 %left AND
135 %right NOT
136 %left CMP_EQUAL CMP_NEQUAL
137 %left CMP_LT CMP_LE CMP_GE CMP_GT
138 %nonassoc CMP_REGEX CMP_NREGEX
139 %nonassoc IN
140 %left CONCAT
141 %nonassoc IS
142 %left '+' '-'
143 %left '*' '/' '%'
144 %left '[' ']'
145 %left '(' ')'
146 %left '.'
148 %type <list> statements
149 %type <node> statement
150 fetch_statement
151 list_statement
152 lookup_statement
153 timeseries_statement
154 matching_clause
155 filter_clause
156 condition
158 %type <m> matcher
159 compare_matcher
161 %type <expr> expression
163 %type <integer> object_type object_type_plural
164 %type <integer> iterable
165 %type <integer> field
167 %type <sstr> cmp
169 %type <data> data
170 interval interval_elem
172 %type <datetime> datetime
173 start_clause end_clause
175 %destructor { free($$); } <str>
176 %destructor { sdb_object_deref(SDB_OBJ($$)); } <node> <m> <expr>
177 %destructor { sdb_data_free_datum(&$$); } <data>
179 %%
181 statements:
182 statements ';' statement
183 {
184 /* only accepted in default parse mode */
185 if (parser_mode != SDB_PARSE_DEFAULT) {
186 sdb_fe_yyerrorf(&yylloc, scanner,
187 YY_("syntax error, unexpected statement, "
188 "expecting %s"), MODE_TO_STRING(parser_mode));
189 sdb_object_deref(SDB_OBJ($3));
190 YYABORT;
191 }
193 if ($3) {
194 sdb_llist_append(pt, SDB_OBJ($3));
195 sdb_object_deref(SDB_OBJ($3));
196 }
197 }
198 |
199 statement
200 {
201 /* only accepted in default parse mode */
202 if (parser_mode != SDB_PARSE_DEFAULT) {
203 sdb_fe_yyerrorf(&yylloc, scanner,
204 YY_("syntax error, unexpected statement, "
205 "expecting %s"), MODE_TO_STRING(parser_mode));
206 sdb_object_deref(SDB_OBJ($1));
207 YYABORT;
208 }
210 if ($1) {
211 sdb_llist_append(pt, SDB_OBJ($1));
212 sdb_object_deref(SDB_OBJ($1));
213 }
214 }
215 |
216 condition
217 {
218 /* only accepted in condition parse mode */
219 if (! (parser_mode & SDB_PARSE_COND)) {
220 sdb_fe_yyerrorf(&yylloc, scanner,
221 YY_("syntax error, unexpected condition, "
222 "expecting %s"), MODE_TO_STRING(parser_mode));
223 sdb_object_deref(SDB_OBJ($1));
224 YYABORT;
225 }
227 if ($1) {
228 sdb_llist_append(pt, SDB_OBJ($1));
229 sdb_object_deref(SDB_OBJ($1));
230 }
231 }
232 |
233 expression
234 {
235 /* only accepted in expression parse mode */
236 if (! (parser_mode & SDB_PARSE_EXPR)) {
237 sdb_fe_yyerrorf(&yylloc, scanner,
238 YY_("syntax error, unexpected expression, "
239 "expecting %s"), MODE_TO_STRING(parser_mode));
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 object_type STRING filter_clause
279 {
280 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
281 conn_fetch_t, conn_fetch_destroy));
282 CONN_FETCH($$)->type = $2;
283 CONN_FETCH($$)->host = $3;
284 CONN_FETCH($$)->name = NULL;
285 CONN_FETCH($$)->filter = CONN_MATCHER($4);
286 $$->cmd = CONNECTION_FETCH;
287 }
288 |
289 FETCH object_type STRING '.' STRING filter_clause
290 {
291 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
292 conn_fetch_t, conn_fetch_destroy));
293 CONN_FETCH($$)->type = $2;
294 CONN_FETCH($$)->host = $3;
295 CONN_FETCH($$)->name = $5;
296 CONN_FETCH($$)->filter = CONN_MATCHER($6);
297 $$->cmd = CONNECTION_FETCH;
298 }
299 ;
301 /*
302 * LIST <type> [FILTER <condition>];
303 *
304 * Returns a list of all hosts in the store.
305 */
306 list_statement:
307 LIST object_type_plural filter_clause
308 {
309 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
310 conn_list_t, conn_list_destroy));
311 CONN_LIST($$)->type = $2;
312 CONN_LIST($$)->filter = CONN_MATCHER($3);
313 $$->cmd = CONNECTION_LIST;
314 }
315 ;
317 /*
318 * LOOKUP <type> MATCHING <condition> [FILTER <condition>];
319 *
320 * Returns detailed information about <type> matching condition.
321 */
322 lookup_statement:
323 LOOKUP HOSTS_T matching_clause filter_clause
324 {
325 /* TODO: support other types as well */
327 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
328 conn_lookup_t, conn_lookup_destroy));
329 CONN_LOOKUP($$)->type = SDB_HOST;
330 CONN_LOOKUP($$)->matcher = CONN_MATCHER($3);
331 CONN_LOOKUP($$)->filter = CONN_MATCHER($4);
332 $$->cmd = CONNECTION_LOOKUP;
333 }
334 ;
336 matching_clause:
337 MATCHING condition { $$ = $2; }
338 |
339 /* empty */ { $$ = NULL; }
341 filter_clause:
342 FILTER condition { $$ = $2; }
343 |
344 /* empty */ { $$ = NULL; }
346 /*
347 * TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>];
348 *
349 * Returns a time-series for the specified host's metric.
350 */
351 timeseries_statement:
352 TIMESERIES STRING '.' STRING start_clause end_clause
353 {
354 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
355 conn_ts_t, conn_ts_destroy));
356 CONN_TS($$)->hostname = $2;
357 CONN_TS($$)->metric = $4;
358 CONN_TS($$)->opts.start = $5;
359 CONN_TS($$)->opts.end = $6;
360 $$->cmd = CONNECTION_TIMESERIES;
361 }
362 ;
364 start_clause:
365 START datetime { $$ = $2; }
366 |
367 /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; }
369 end_clause:
370 END datetime { $$ = $2; }
371 |
372 /* empty */ { $$ = sdb_gettime(); }
374 /*
375 * Basic expressions.
376 */
378 condition:
379 matcher
380 {
381 if (! $1) {
382 /* TODO: improve error reporting */
383 sdb_fe_yyerror(&yylloc, scanner,
384 YY_("syntax error, invalid condition"));
385 YYABORT;
386 }
388 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
389 conn_matcher_t, conn_matcher_destroy));
390 $$->cmd = CONNECTION_MATCHER;
391 CONN_MATCHER($$)->matcher = $1;
392 }
393 ;
395 matcher:
396 '(' matcher ')'
397 {
398 $$ = $2;
399 }
400 |
401 matcher AND matcher
402 {
403 $$ = sdb_store_con_matcher($1, $3);
404 sdb_object_deref(SDB_OBJ($1));
405 sdb_object_deref(SDB_OBJ($3));
406 }
407 |
408 matcher OR matcher
409 {
410 $$ = sdb_store_dis_matcher($1, $3);
411 sdb_object_deref(SDB_OBJ($1));
412 sdb_object_deref(SDB_OBJ($3));
413 }
414 |
415 NOT matcher
416 {
417 $$ = sdb_store_inv_matcher($2);
418 sdb_object_deref(SDB_OBJ($2));
419 }
420 |
421 compare_matcher
422 {
423 $$ = $1;
424 }
425 ;
427 compare_matcher:
428 expression cmp expression
429 {
430 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op($2);
431 assert(cb); /* else, the grammar accepts invalid 'cmp' */
432 $$ = cb($1, $3);
433 sdb_object_deref(SDB_OBJ($1));
434 sdb_object_deref(SDB_OBJ($3));
435 }
436 |
437 ANY iterable cmp expression
438 {
439 $$ = name_iter_matcher(MATCHER_ANY, $2, $3, $4);
440 sdb_object_deref(SDB_OBJ($4));
441 }
442 |
443 ALL iterable cmp expression
444 {
445 $$ = name_iter_matcher(MATCHER_ALL, $2, $3, $4);
446 sdb_object_deref(SDB_OBJ($4));
447 }
448 |
449 expression IS NULL_T
450 {
451 $$ = sdb_store_isnull_matcher($1);
452 sdb_object_deref(SDB_OBJ($1));
453 }
454 |
455 expression IS NOT NULL_T
456 {
457 $$ = sdb_store_isnnull_matcher($1);
458 sdb_object_deref(SDB_OBJ($1));
459 }
460 |
461 expression IN expression
462 {
463 $$ = sdb_store_in_matcher($1, $3);
464 sdb_object_deref(SDB_OBJ($1));
465 sdb_object_deref(SDB_OBJ($3));
466 }
467 ;
469 expression:
470 '(' expression ')'
471 {
472 $$ = $2;
473 }
474 |
475 expression '+' expression
476 {
477 $$ = sdb_store_expr_create(SDB_DATA_ADD, $1, $3);
478 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
479 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
480 }
481 |
482 expression '-' expression
483 {
484 $$ = sdb_store_expr_create(SDB_DATA_SUB, $1, $3);
485 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
486 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
487 }
488 |
489 expression '*' expression
490 {
491 $$ = sdb_store_expr_create(SDB_DATA_MUL, $1, $3);
492 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
493 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
494 }
495 |
496 expression '/' expression
497 {
498 $$ = sdb_store_expr_create(SDB_DATA_DIV, $1, $3);
499 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
500 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
501 }
502 |
503 expression '%' expression
504 {
505 $$ = sdb_store_expr_create(SDB_DATA_MOD, $1, $3);
506 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
507 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
508 }
509 |
510 expression CONCAT expression
511 {
512 $$ = sdb_store_expr_create(SDB_DATA_CONCAT, $1, $3);
513 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
514 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
515 }
516 |
517 HOST_T
518 {
519 /* TODO: this only works as long as queries
520 * are limited to hosts */
521 $$ = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
522 }
523 |
524 field
525 {
526 $$ = sdb_store_expr_fieldvalue($1);
527 }
528 |
529 ATTRIBUTE_T '[' STRING ']'
530 {
531 $$ = sdb_store_expr_attrvalue($3);
532 free($3); $3 = NULL;
533 }
534 |
535 data
536 {
537 $$ = sdb_store_expr_constvalue(&$1);
538 sdb_data_free_datum(&$1);
539 }
540 ;
542 object_type:
543 HOST_T { $$ = SDB_HOST; }
544 |
545 SERVICE_T { $$ = SDB_SERVICE; }
546 |
547 METRIC_T { $$ = SDB_METRIC; }
548 ;
550 object_type_plural:
551 HOSTS_T { $$ = SDB_HOST; }
552 |
553 SERVICES_T { $$ = SDB_SERVICE; }
554 |
555 METRICS_T { $$ = SDB_METRIC; }
556 ;
558 iterable:
559 SERVICE_T { $$ = SDB_SERVICE; }
560 |
561 METRIC_T { $$ = SDB_METRIC; }
562 |
563 ATTRIBUTE_T { $$ = SDB_ATTRIBUTE; }
564 ;
566 field:
567 NAME_T { $$ = SDB_FIELD_NAME; }
568 |
569 LAST_UPDATE_T { $$ = SDB_FIELD_LAST_UPDATE; }
570 |
571 AGE_T { $$ = SDB_FIELD_AGE; }
572 |
573 INTERVAL_T { $$ = SDB_FIELD_INTERVAL; }
574 |
575 BACKEND_T { $$ = SDB_FIELD_BACKEND; }
576 ;
578 cmp:
579 CMP_EQUAL { $$ = "="; }
580 |
581 CMP_NEQUAL { $$ = "!="; }
582 |
583 CMP_REGEX { $$ = "=~"; }
584 |
585 CMP_NREGEX { $$ = "!~"; }
586 |
587 CMP_LT { $$ = "<"; }
588 |
589 CMP_LE { $$ = "<="; }
590 |
591 CMP_GE { $$ = ">="; }
592 |
593 CMP_GT { $$ = ">"; }
594 ;
596 data:
597 STRING { $$.type = SDB_TYPE_STRING; $$.data.string = $1; }
598 |
599 INTEGER { $$ = $1; }
600 |
601 FLOAT { $$ = $1; }
602 |
603 datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; }
604 |
605 interval { $$ = $1; }
606 ;
608 datetime:
609 DATE TIME { $$ = $1 + $2; }
610 |
611 DATE { $$ = $1; }
612 |
613 TIME { $$ = $1; }
614 ;
616 interval:
617 interval interval_elem
618 {
619 $$.data.datetime = $1.data.datetime + $2.data.datetime;
620 }
621 |
622 interval_elem { $$ = $1; }
623 ;
625 interval_elem:
626 INTEGER IDENTIFIER
627 {
628 sdb_time_t unit = 1;
630 unit = sdb_strpunit($2);
631 if (! unit) {
632 sdb_fe_yyerrorf(&yylloc, scanner,
633 YY_("invalid time unit %s"), $2);
634 free($2); $2 = NULL;
635 YYABORT;
636 }
637 free($2); $2 = NULL;
639 $$.type = SDB_TYPE_DATETIME;
640 $$.data.datetime = (sdb_time_t)$1.data.integer * unit;
642 if ($1.data.integer < 0) {
643 sdb_fe_yyerror(&yylloc, scanner,
644 YY_("syntax error, negative intervals not supported"));
645 YYABORT;
646 }
647 }
648 ;
650 %%
652 void
653 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg)
654 {
655 sdb_log(SDB_LOG_ERR, "frontend: parse error: %s", msg);
656 } /* sdb_fe_yyerror */
658 void
659 sdb_fe_yyerrorf(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *fmt, ...)
660 {
661 va_list ap;
662 va_start(ap, fmt);
663 sdb_vlog(SDB_LOG_ERR, fmt, ap);
664 va_end(ap);
665 } /* sdb_fe_yyerrorf */
667 static sdb_store_matcher_t *
668 name_iter_matcher(int m_type, int type, const char *cmp,
669 sdb_store_expr_t *expr)
670 {
671 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op(cmp);
672 sdb_store_expr_t *e;
673 sdb_store_matcher_t *m, *tmp = NULL;
674 assert(cb);
676 /* TODO: this only works as long as queries
677 * are limited to hosts */
678 if (type == SDB_HOST) {
679 return NULL;
680 }
682 e = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
683 m = cb(e, expr);
684 if (m_type == MATCHER_ANY)
685 tmp = sdb_store_any_matcher(type, m);
686 else if (m_type == MATCHER_ALL)
687 tmp = sdb_store_all_matcher(type, m);
688 sdb_object_deref(SDB_OBJ(m));
689 sdb_object_deref(SDB_OBJ(e));
690 return tmp;
691 } /* name_iter_matcher */
693 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */