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(char *type_name, char *cmp, sdb_store_expr_t *expr);
53 static sdb_store_matcher_t *
54 name_iter_matcher(char *type_name, char *cmp, sdb_store_expr_t *expr);
56 /*
57 * public API
58 */
60 int
61 sdb_fe_yylex(YYSTYPE *yylval, YYLTYPE *yylloc, sdb_fe_yyscan_t yyscanner);
63 sdb_fe_yyextra_t *
64 sdb_fe_yyget_extra(sdb_fe_yyscan_t scanner);
66 void
67 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
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;
95 sdb_data_t data;
96 sdb_time_t datetime;
98 sdb_llist_t *list;
99 sdb_conn_node_t *node;
101 sdb_store_matcher_t *m;
102 sdb_store_expr_t *expr;
103 }
105 %start statements
107 %token SCANNER_ERROR
109 %token AND OR IS NOT MATCHING FILTER
110 %token CMP_EQUAL CMP_NEQUAL CMP_REGEX CMP_NREGEX
111 %token CMP_LT CMP_LE CMP_GE CMP_GT ANY IN
112 %token CONCAT
114 %token START END
116 /* NULL token */
117 %token NULL_T
119 %token FETCH LIST LOOKUP TIMESERIES
121 %token <str> IDENTIFIER STRING
123 %token <data> INTEGER FLOAT
125 %token <datetime> DATE TIME
127 /* Precedence (lowest first): */
128 %left OR
129 %left AND
130 %right NOT
131 %left CMP_EQUAL CMP_NEQUAL
132 %left CMP_LT CMP_LE CMP_GE CMP_GT
133 %nonassoc CMP_REGEX CMP_NREGEX
134 %nonassoc IN
135 %left CONCAT
136 %nonassoc IS
137 %left '+' '-'
138 %left '*' '/' '%'
139 %left '[' ']'
140 %left '(' ')'
141 %left '.'
143 %type <list> statements
144 %type <node> statement
145 fetch_statement
146 list_statement
147 lookup_statement
148 timeseries_statement
149 matching_clause
150 filter_clause
151 condition
153 %type <m> matcher
154 compare_matcher
156 %type <expr> expression
158 %type <sstr> cmp
160 %type <data> data
161 interval interval_elem
163 %type <datetime> datetime
164 start_clause end_clause
166 %destructor { free($$); } <str>
167 %destructor { sdb_object_deref(SDB_OBJ($$)); } <node> <m> <expr>
168 %destructor { sdb_data_free_datum(&$$); } <data>
170 %%
172 statements:
173 statements ';' statement
174 {
175 /* only accepted in default parse mode */
176 if (parser_mode != SDB_PARSE_DEFAULT) {
177 char errmsg[1024];
178 snprintf(errmsg, sizeof(errmsg),
179 YY_("syntax error, unexpected statement, "
180 "expecting %s"), MODE_TO_STRING(parser_mode));
181 sdb_fe_yyerror(&yylloc, scanner, errmsg);
182 sdb_object_deref(SDB_OBJ($3));
183 YYABORT;
184 }
186 if ($3) {
187 sdb_llist_append(pt, SDB_OBJ($3));
188 sdb_object_deref(SDB_OBJ($3));
189 }
190 }
191 |
192 statement
193 {
194 /* only accepted in default parse mode */
195 if (parser_mode != SDB_PARSE_DEFAULT) {
196 char errmsg[1024];
197 snprintf(errmsg, sizeof(errmsg),
198 YY_("syntax error, unexpected statement, "
199 "expecting %s"), MODE_TO_STRING(parser_mode));
200 sdb_fe_yyerror(&yylloc, scanner, errmsg);
201 sdb_object_deref(SDB_OBJ($1));
202 YYABORT;
203 }
205 if ($1) {
206 sdb_llist_append(pt, SDB_OBJ($1));
207 sdb_object_deref(SDB_OBJ($1));
208 }
209 }
210 |
211 condition
212 {
213 /* only accepted in condition parse mode */
214 if (! (parser_mode & SDB_PARSE_COND)) {
215 char errmsg[1024];
216 snprintf(errmsg, sizeof(errmsg),
217 YY_("syntax error, unexpected condition, "
218 "expecting %s"), MODE_TO_STRING(parser_mode));
219 sdb_fe_yyerror(&yylloc, scanner, errmsg);
220 sdb_object_deref(SDB_OBJ($1));
221 YYABORT;
222 }
224 if ($1) {
225 sdb_llist_append(pt, SDB_OBJ($1));
226 sdb_object_deref(SDB_OBJ($1));
227 }
228 }
229 |
230 expression
231 {
232 /* only accepted in expression parse mode */
233 if (! (parser_mode & SDB_PARSE_EXPR)) {
234 char errmsg[1024];
235 snprintf(errmsg, sizeof(errmsg),
236 YY_("syntax error, unexpected expression, "
237 "expecting %s"), MODE_TO_STRING(parser_mode));
238 sdb_fe_yyerror(&yylloc, scanner, errmsg);
239 sdb_object_deref(SDB_OBJ($1));
240 YYABORT;
241 }
243 if ($1) {
244 sdb_conn_node_t *n;
245 n = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
246 conn_expr_t, conn_expr_destroy));
247 n->cmd = CONNECTION_EXPR;
248 CONN_EXPR(n)->expr = $1;
250 sdb_llist_append(pt, SDB_OBJ(n));
251 sdb_object_deref(SDB_OBJ(n));
252 }
253 }
254 ;
256 statement:
257 fetch_statement
258 |
259 list_statement
260 |
261 lookup_statement
262 |
263 timeseries_statement
264 |
265 /* empty */
266 {
267 $$ = NULL;
268 }
269 ;
271 /*
272 * FETCH <type> <hostname> [FILTER <condition>];
273 *
274 * Retrieve detailed information about a single host.
275 */
276 fetch_statement:
277 FETCH IDENTIFIER STRING filter_clause
278 {
279 /* TODO: support other types as well */
280 if (strcasecmp($2, "host")) {
281 char errmsg[strlen($2) + 32];
282 snprintf(errmsg, sizeof(errmsg),
283 YY_("unknown data-source %s"), $2);
284 sdb_fe_yyerror(&yylloc, scanner, errmsg);
285 free($2); $2 = NULL;
286 free($3); $3 = NULL;
287 sdb_object_deref(SDB_OBJ($4));
288 YYABORT;
289 }
291 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
292 conn_fetch_t, conn_fetch_destroy));
293 CONN_FETCH($$)->type = SDB_HOST;
294 CONN_FETCH($$)->name = $3;
295 CONN_FETCH($$)->filter = CONN_MATCHER($4);
296 $$->cmd = CONNECTION_FETCH;
297 free($2); $2 = NULL;
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 IDENTIFIER filter_clause
308 {
309 int type = sdb_store_parse_object_type_plural($2);
310 if ((type < 0) || (type == SDB_ATTRIBUTE)) {
311 char errmsg[strlen($2) + 32];
312 snprintf(errmsg, sizeof(errmsg),
313 YY_("unknown data-source %s"), $2);
314 sdb_fe_yyerror(&yylloc, scanner, errmsg);
315 free($2); $2 = NULL;
316 sdb_object_deref(SDB_OBJ($3));
317 YYABORT;
318 }
320 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
321 conn_list_t, conn_list_destroy));
322 CONN_LIST($$)->type = type;
323 CONN_LIST($$)->filter = CONN_MATCHER($3);
324 $$->cmd = CONNECTION_LIST;
325 free($2); $2 = NULL;
326 }
327 ;
329 /*
330 * LOOKUP <type> MATCHING <condition> [FILTER <condition>];
331 *
332 * Returns detailed information about <type> matching condition.
333 */
334 lookup_statement:
335 LOOKUP IDENTIFIER matching_clause filter_clause
336 {
337 /* TODO: support other types as well */
338 if (strcasecmp($2, "hosts")) {
339 char errmsg[strlen($2) + 32];
340 snprintf(errmsg, sizeof(errmsg),
341 YY_("unknown data-source %s"), $2);
342 sdb_fe_yyerror(&yylloc, scanner, errmsg);
343 free($2); $2 = NULL;
344 sdb_object_deref(SDB_OBJ($3));
345 sdb_object_deref(SDB_OBJ($4));
346 YYABORT;
347 }
349 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
350 conn_lookup_t, conn_lookup_destroy));
351 CONN_LOOKUP($$)->type = SDB_HOST;
352 CONN_LOOKUP($$)->matcher = CONN_MATCHER($3);
353 CONN_LOOKUP($$)->filter = CONN_MATCHER($4);
354 $$->cmd = CONNECTION_LOOKUP;
355 free($2); $2 = NULL;
356 }
357 ;
359 matching_clause:
360 MATCHING condition { $$ = $2; }
361 |
362 /* empty */ { $$ = NULL; }
364 filter_clause:
365 FILTER condition { $$ = $2; }
366 |
367 /* empty */ { $$ = NULL; }
369 /*
370 * TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>];
371 *
372 * Returns a time-series for the specified host's metric.
373 */
374 timeseries_statement:
375 TIMESERIES STRING '.' STRING start_clause end_clause
376 {
377 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
378 conn_ts_t, conn_ts_destroy));
379 CONN_TS($$)->hostname = $2;
380 CONN_TS($$)->metric = $4;
381 CONN_TS($$)->opts.start = $5;
382 CONN_TS($$)->opts.end = $6;
383 $$->cmd = CONNECTION_TIMESERIES;
384 }
385 ;
387 start_clause:
388 START datetime { $$ = $2; }
389 |
390 /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; }
392 end_clause:
393 END datetime { $$ = $2; }
394 |
395 /* empty */ { $$ = sdb_gettime(); }
397 /*
398 * Basic expressions.
399 */
401 condition:
402 matcher
403 {
404 if (! $1) {
405 /* TODO: improve error reporting */
406 sdb_fe_yyerror(&yylloc, scanner,
407 YY_("syntax error, invalid condition"));
408 YYABORT;
409 }
411 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
412 conn_matcher_t, conn_matcher_destroy));
413 $$->cmd = CONNECTION_MATCHER;
414 CONN_MATCHER($$)->matcher = $1;
415 }
416 ;
418 matcher:
419 '(' matcher ')'
420 {
421 $$ = $2;
422 }
423 |
424 matcher AND matcher
425 {
426 $$ = sdb_store_con_matcher($1, $3);
427 sdb_object_deref(SDB_OBJ($1));
428 sdb_object_deref(SDB_OBJ($3));
429 }
430 |
431 matcher OR matcher
432 {
433 $$ = sdb_store_dis_matcher($1, $3);
434 sdb_object_deref(SDB_OBJ($1));
435 sdb_object_deref(SDB_OBJ($3));
436 }
437 |
438 NOT matcher
439 {
440 $$ = sdb_store_inv_matcher($2);
441 sdb_object_deref(SDB_OBJ($2));
442 }
443 |
444 compare_matcher
445 {
446 $$ = $1;
447 }
448 ;
450 compare_matcher:
451 expression cmp expression
452 {
453 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op($2);
454 assert(cb); /* else, the grammar accepts invalid 'cmp' */
455 $$ = cb($1, $3);
456 sdb_object_deref(SDB_OBJ($1));
457 sdb_object_deref(SDB_OBJ($3));
458 }
459 |
460 IDENTIFIER cmp expression
461 {
462 $$ = name_matcher($1, $2, $3);
463 free($1); $1 = NULL;
464 sdb_object_deref(SDB_OBJ($3));
465 }
466 |
467 ANY IDENTIFIER cmp expression
468 {
469 $$ = name_iter_matcher($2, $3, $4);
470 free($2); $2 = NULL;
471 sdb_object_deref(SDB_OBJ($4));
472 }
473 |
474 expression IS NULL_T
475 {
476 $$ = sdb_store_isnull_matcher($1);
477 sdb_object_deref(SDB_OBJ($1));
478 }
479 |
480 expression IS NOT NULL_T
481 {
482 $$ = sdb_store_isnnull_matcher($1);
483 sdb_object_deref(SDB_OBJ($1));
484 }
485 |
486 expression IN expression
487 {
488 $$ = sdb_store_in_matcher($1, $3);
489 sdb_object_deref(SDB_OBJ($1));
490 sdb_object_deref(SDB_OBJ($3));
491 }
492 ;
494 expression:
495 '(' expression ')'
496 {
497 $$ = $2;
498 }
499 |
500 expression '+' expression
501 {
502 $$ = sdb_store_expr_create(SDB_DATA_ADD, $1, $3);
503 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
504 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
505 }
506 |
507 expression '-' expression
508 {
509 $$ = sdb_store_expr_create(SDB_DATA_SUB, $1, $3);
510 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
511 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
512 }
513 |
514 expression '*' expression
515 {
516 $$ = sdb_store_expr_create(SDB_DATA_MUL, $1, $3);
517 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
518 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
519 }
520 |
521 expression '/' expression
522 {
523 $$ = sdb_store_expr_create(SDB_DATA_DIV, $1, $3);
524 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
525 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
526 }
527 |
528 expression '%' expression
529 {
530 $$ = sdb_store_expr_create(SDB_DATA_MOD, $1, $3);
531 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
532 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
533 }
534 |
535 expression CONCAT expression
536 {
537 $$ = sdb_store_expr_create(SDB_DATA_CONCAT, $1, $3);
538 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
539 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
540 }
541 |
542 '.' IDENTIFIER
543 {
544 int field = sdb_store_parse_field_name($2);
545 free($2); $2 = NULL;
546 $$ = sdb_store_expr_fieldvalue(field);
547 }
548 |
549 IDENTIFIER '[' STRING ']'
550 {
551 if (strcasecmp($1, "attribute")) {
552 char errmsg[strlen($1) + strlen($3) + 32];
553 snprintf(errmsg, sizeof(errmsg),
554 YY_("unknown value %s[%s]"), $1, $3);
555 sdb_fe_yyerror(&yylloc, scanner, errmsg);
556 free($1); $1 = NULL;
557 free($3); $3 = NULL;
558 YYABORT;
559 }
560 $$ = sdb_store_expr_attrvalue($3);
561 free($1); $1 = NULL;
562 free($3); $3 = NULL;
563 }
564 |
565 data
566 {
567 $$ = sdb_store_expr_constvalue(&$1);
568 sdb_data_free_datum(&$1);
569 }
570 ;
572 cmp:
573 CMP_EQUAL { $$ = "="; }
574 |
575 CMP_NEQUAL { $$ = "!="; }
576 |
577 CMP_REGEX { $$ = "=~"; }
578 |
579 CMP_NREGEX { $$ = "!~"; }
580 |
581 CMP_LT { $$ = "<"; }
582 |
583 CMP_LE { $$ = "<="; }
584 |
585 CMP_GE { $$ = ">="; }
586 |
587 CMP_GT { $$ = ">"; }
588 ;
590 data:
591 STRING { $$.type = SDB_TYPE_STRING; $$.data.string = $1; }
592 |
593 INTEGER { $$ = $1; }
594 |
595 FLOAT { $$ = $1; }
596 |
597 datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; }
598 |
599 interval { $$ = $1; }
600 ;
602 datetime:
603 DATE TIME { $$ = $1 + $2; }
604 |
605 DATE { $$ = $1; }
606 |
607 TIME { $$ = $1; }
608 ;
610 interval:
611 interval interval_elem
612 {
613 $$.data.datetime = $1.data.datetime + $2.data.datetime;
614 }
615 |
616 interval_elem { $$ = $1; }
617 ;
619 interval_elem:
620 INTEGER IDENTIFIER
621 {
622 sdb_time_t unit = 1;
624 unit = sdb_strpunit($2);
625 if (! unit) {
626 char errmsg[strlen($2) + 32];
627 snprintf(errmsg, sizeof(errmsg),
628 YY_("invalid time unit %s"), $2);
629 sdb_fe_yyerror(&yylloc, scanner, errmsg);
630 free($2); $2 = NULL;
631 YYABORT;
632 }
633 free($2); $2 = NULL;
635 $$.type = SDB_TYPE_DATETIME;
636 $$.data.datetime = (sdb_time_t)$1.data.integer * unit;
638 if ($1.data.integer < 0) {
639 sdb_fe_yyerror(&yylloc, scanner,
640 YY_("syntax error, negative intervals not supported"));
641 YYABORT;
642 }
643 }
644 ;
646 %%
648 void
649 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg)
650 {
651 sdb_log(SDB_LOG_ERR, "frontend: parse error: %s", msg);
652 } /* sdb_fe_yyerror */
654 static sdb_store_matcher_t *
655 name_matcher(char *type_name, char *cmp, sdb_store_expr_t *expr)
656 {
657 int type = sdb_store_parse_object_type(type_name);
658 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op(cmp);
659 sdb_store_expr_t *e;
660 sdb_store_matcher_t *m;
661 assert(cb);
663 /* TODO: this only works as long as queries
664 * are limited to hosts */
665 if (type != SDB_HOST)
666 return NULL;
668 e = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
669 m = cb(e, expr);
670 sdb_object_deref(SDB_OBJ(e));
671 return m;
672 } /* name_matcher */
674 static sdb_store_matcher_t *
675 name_iter_matcher(char *type_name, char *cmp, sdb_store_expr_t *expr)
676 {
677 int type = sdb_store_parse_object_type(type_name);
678 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op(cmp);
679 sdb_store_expr_t *e;
680 sdb_store_matcher_t *m, *tmp;
681 assert(cb);
683 /* TODO: this only works as long as queries
684 * are limited to hosts */
685 if (type == SDB_HOST) {
686 return NULL;
687 }
689 e = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
690 m = cb(e, expr);
691 tmp = sdb_store_any_matcher(type, m);
692 sdb_object_deref(SDB_OBJ(m));
693 sdb_object_deref(SDB_OBJ(e));
694 return tmp;
695 } /* name_iter_matcher */
697 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */