fed30d238f613fe92f301bd3a5ae695d26a10d9f
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, const char *type_name, 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);
67 /* quick access to the current parse tree */
68 #define pt sdb_fe_yyget_extra(scanner)->parsetree
70 /* quick access to the parser mode */
71 #define parser_mode sdb_fe_yyget_extra(scanner)->mode
73 #define MODE_TO_STRING(m) \
74 (((m) == SDB_PARSE_DEFAULT) ? "statement" \
75 : ((m) == SDB_PARSE_COND) ? "condition" \
76 : ((m) == SDB_PARSE_EXPR) ? "expression" \
77 : "UNKNOWN")
79 %}
81 %pure-parser
82 %lex-param {sdb_fe_yyscan_t scanner}
83 %parse-param {sdb_fe_yyscan_t scanner}
84 %locations
85 %error-verbose
86 %expect 0
87 %name-prefix "sdb_fe_yy"
89 %union {
90 const char *sstr; /* static string */
91 char *str;
93 sdb_data_t data;
94 sdb_time_t datetime;
96 sdb_llist_t *list;
97 sdb_conn_node_t *node;
99 sdb_store_matcher_t *m;
100 sdb_store_expr_t *expr;
101 }
103 %start statements
105 %token SCANNER_ERROR
107 %token AND OR IS NOT MATCHING FILTER
108 %token CMP_EQUAL CMP_NEQUAL CMP_REGEX CMP_NREGEX
109 %token CMP_LT CMP_LE CMP_GE CMP_GT ALL ANY IN
110 %token CONCAT
112 %token START END
114 /* NULL token */
115 %token NULL_T
117 %token FETCH LIST LOOKUP TIMESERIES
119 %token <str> IDENTIFIER STRING
121 %token <data> INTEGER FLOAT
123 %token <datetime> DATE TIME
125 /* Precedence (lowest first): */
126 %left OR
127 %left AND
128 %right NOT
129 %left CMP_EQUAL CMP_NEQUAL
130 %left CMP_LT CMP_LE CMP_GE CMP_GT
131 %nonassoc CMP_REGEX CMP_NREGEX
132 %nonassoc IN
133 %left CONCAT
134 %nonassoc IS
135 %left '+' '-'
136 %left '*' '/' '%'
137 %left '[' ']'
138 %left '(' ')'
139 %left '.'
141 %type <list> statements
142 %type <node> statement
143 fetch_statement
144 list_statement
145 lookup_statement
146 timeseries_statement
147 matching_clause
148 filter_clause
149 condition
151 %type <m> matcher
152 compare_matcher
154 %type <expr> expression
156 %type <sstr> cmp
158 %type <data> data
159 interval interval_elem
161 %type <datetime> datetime
162 start_clause end_clause
164 %destructor { free($$); } <str>
165 %destructor { sdb_object_deref(SDB_OBJ($$)); } <node> <m> <expr>
166 %destructor { sdb_data_free_datum(&$$); } <data>
168 %%
170 statements:
171 statements ';' statement
172 {
173 /* only accepted in default parse mode */
174 if (parser_mode != SDB_PARSE_DEFAULT) {
175 char errmsg[1024];
176 snprintf(errmsg, sizeof(errmsg),
177 YY_("syntax error, unexpected statement, "
178 "expecting %s"), MODE_TO_STRING(parser_mode));
179 sdb_fe_yyerror(&yylloc, scanner, errmsg);
180 sdb_object_deref(SDB_OBJ($3));
181 YYABORT;
182 }
184 if ($3) {
185 sdb_llist_append(pt, SDB_OBJ($3));
186 sdb_object_deref(SDB_OBJ($3));
187 }
188 }
189 |
190 statement
191 {
192 /* only accepted in default parse mode */
193 if (parser_mode != SDB_PARSE_DEFAULT) {
194 char errmsg[1024];
195 snprintf(errmsg, sizeof(errmsg),
196 YY_("syntax error, unexpected statement, "
197 "expecting %s"), MODE_TO_STRING(parser_mode));
198 sdb_fe_yyerror(&yylloc, scanner, errmsg);
199 sdb_object_deref(SDB_OBJ($1));
200 YYABORT;
201 }
203 if ($1) {
204 sdb_llist_append(pt, SDB_OBJ($1));
205 sdb_object_deref(SDB_OBJ($1));
206 }
207 }
208 |
209 condition
210 {
211 /* only accepted in condition parse mode */
212 if (! (parser_mode & SDB_PARSE_COND)) {
213 char errmsg[1024];
214 snprintf(errmsg, sizeof(errmsg),
215 YY_("syntax error, unexpected condition, "
216 "expecting %s"), MODE_TO_STRING(parser_mode));
217 sdb_fe_yyerror(&yylloc, scanner, errmsg);
218 sdb_object_deref(SDB_OBJ($1));
219 YYABORT;
220 }
222 if ($1) {
223 sdb_llist_append(pt, SDB_OBJ($1));
224 sdb_object_deref(SDB_OBJ($1));
225 }
226 }
227 |
228 expression
229 {
230 /* only accepted in expression parse mode */
231 if (! (parser_mode & SDB_PARSE_EXPR)) {
232 char errmsg[1024];
233 snprintf(errmsg, sizeof(errmsg),
234 YY_("syntax error, unexpected expression, "
235 "expecting %s"), MODE_TO_STRING(parser_mode));
236 sdb_fe_yyerror(&yylloc, scanner, errmsg);
237 sdb_object_deref(SDB_OBJ($1));
238 YYABORT;
239 }
241 if ($1) {
242 sdb_conn_node_t *n;
243 n = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
244 conn_expr_t, conn_expr_destroy));
245 n->cmd = CONNECTION_EXPR;
246 CONN_EXPR(n)->expr = $1;
248 sdb_llist_append(pt, SDB_OBJ(n));
249 sdb_object_deref(SDB_OBJ(n));
250 }
251 }
252 ;
254 statement:
255 fetch_statement
256 |
257 list_statement
258 |
259 lookup_statement
260 |
261 timeseries_statement
262 |
263 /* empty */
264 {
265 $$ = NULL;
266 }
267 ;
269 /*
270 * FETCH <type> <hostname> [FILTER <condition>];
271 *
272 * Retrieve detailed information about a single host.
273 */
274 fetch_statement:
275 FETCH IDENTIFIER STRING filter_clause
276 {
277 /* TODO: support other types as well */
278 if (strcasecmp($2, "host")) {
279 char errmsg[strlen($2) + 32];
280 snprintf(errmsg, sizeof(errmsg),
281 YY_("unknown data-source %s"), $2);
282 sdb_fe_yyerror(&yylloc, scanner, errmsg);
283 free($2); $2 = NULL;
284 free($3); $3 = NULL;
285 sdb_object_deref(SDB_OBJ($4));
286 YYABORT;
287 }
289 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
290 conn_fetch_t, conn_fetch_destroy));
291 CONN_FETCH($$)->type = SDB_HOST;
292 CONN_FETCH($$)->name = $3;
293 CONN_FETCH($$)->filter = CONN_MATCHER($4);
294 $$->cmd = CONNECTION_FETCH;
295 free($2); $2 = NULL;
296 }
297 ;
299 /*
300 * LIST <type> [FILTER <condition>];
301 *
302 * Returns a list of all hosts in the store.
303 */
304 list_statement:
305 LIST IDENTIFIER filter_clause
306 {
307 int type = sdb_store_parse_object_type_plural($2);
308 if ((type < 0) || (type == SDB_ATTRIBUTE)) {
309 char errmsg[strlen($2) + 32];
310 snprintf(errmsg, sizeof(errmsg),
311 YY_("unknown data-source %s"), $2);
312 sdb_fe_yyerror(&yylloc, scanner, errmsg);
313 free($2); $2 = NULL;
314 sdb_object_deref(SDB_OBJ($3));
315 YYABORT;
316 }
318 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
319 conn_list_t, conn_list_destroy));
320 CONN_LIST($$)->type = type;
321 CONN_LIST($$)->filter = CONN_MATCHER($3);
322 $$->cmd = CONNECTION_LIST;
323 free($2); $2 = NULL;
324 }
325 ;
327 /*
328 * LOOKUP <type> MATCHING <condition> [FILTER <condition>];
329 *
330 * Returns detailed information about <type> matching condition.
331 */
332 lookup_statement:
333 LOOKUP IDENTIFIER matching_clause filter_clause
334 {
335 /* TODO: support other types as well */
336 if (strcasecmp($2, "hosts")) {
337 char errmsg[strlen($2) + 32];
338 snprintf(errmsg, sizeof(errmsg),
339 YY_("unknown data-source %s"), $2);
340 sdb_fe_yyerror(&yylloc, scanner, errmsg);
341 free($2); $2 = NULL;
342 sdb_object_deref(SDB_OBJ($3));
343 sdb_object_deref(SDB_OBJ($4));
344 YYABORT;
345 }
347 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
348 conn_lookup_t, conn_lookup_destroy));
349 CONN_LOOKUP($$)->type = SDB_HOST;
350 CONN_LOOKUP($$)->matcher = CONN_MATCHER($3);
351 CONN_LOOKUP($$)->filter = CONN_MATCHER($4);
352 $$->cmd = CONNECTION_LOOKUP;
353 free($2); $2 = NULL;
354 }
355 ;
357 matching_clause:
358 MATCHING condition { $$ = $2; }
359 |
360 /* empty */ { $$ = NULL; }
362 filter_clause:
363 FILTER condition { $$ = $2; }
364 |
365 /* empty */ { $$ = NULL; }
367 /*
368 * TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>];
369 *
370 * Returns a time-series for the specified host's metric.
371 */
372 timeseries_statement:
373 TIMESERIES STRING '.' STRING start_clause end_clause
374 {
375 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
376 conn_ts_t, conn_ts_destroy));
377 CONN_TS($$)->hostname = $2;
378 CONN_TS($$)->metric = $4;
379 CONN_TS($$)->opts.start = $5;
380 CONN_TS($$)->opts.end = $6;
381 $$->cmd = CONNECTION_TIMESERIES;
382 }
383 ;
385 start_clause:
386 START datetime { $$ = $2; }
387 |
388 /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; }
390 end_clause:
391 END datetime { $$ = $2; }
392 |
393 /* empty */ { $$ = sdb_gettime(); }
395 /*
396 * Basic expressions.
397 */
399 condition:
400 matcher
401 {
402 if (! $1) {
403 /* TODO: improve error reporting */
404 sdb_fe_yyerror(&yylloc, scanner,
405 YY_("syntax error, invalid condition"));
406 YYABORT;
407 }
409 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
410 conn_matcher_t, conn_matcher_destroy));
411 $$->cmd = CONNECTION_MATCHER;
412 CONN_MATCHER($$)->matcher = $1;
413 }
414 ;
416 matcher:
417 '(' matcher ')'
418 {
419 $$ = $2;
420 }
421 |
422 matcher AND matcher
423 {
424 $$ = sdb_store_con_matcher($1, $3);
425 sdb_object_deref(SDB_OBJ($1));
426 sdb_object_deref(SDB_OBJ($3));
427 }
428 |
429 matcher OR matcher
430 {
431 $$ = sdb_store_dis_matcher($1, $3);
432 sdb_object_deref(SDB_OBJ($1));
433 sdb_object_deref(SDB_OBJ($3));
434 }
435 |
436 NOT matcher
437 {
438 $$ = sdb_store_inv_matcher($2);
439 sdb_object_deref(SDB_OBJ($2));
440 }
441 |
442 compare_matcher
443 {
444 $$ = $1;
445 }
446 ;
448 compare_matcher:
449 expression cmp expression
450 {
451 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op($2);
452 assert(cb); /* else, the grammar accepts invalid 'cmp' */
453 $$ = cb($1, $3);
454 sdb_object_deref(SDB_OBJ($1));
455 sdb_object_deref(SDB_OBJ($3));
456 }
457 |
458 ANY IDENTIFIER cmp expression
459 {
460 $$ = name_iter_matcher(MATCHER_ANY, $2, $3, $4);
461 free($2); $2 = NULL;
462 sdb_object_deref(SDB_OBJ($4));
463 }
464 |
465 ALL IDENTIFIER cmp expression
466 {
467 $$ = name_iter_matcher(MATCHER_ALL, $2, $3, $4);
468 free($2); $2 = NULL;
469 sdb_object_deref(SDB_OBJ($4));
470 }
471 |
472 expression IS NULL_T
473 {
474 $$ = sdb_store_isnull_matcher($1);
475 sdb_object_deref(SDB_OBJ($1));
476 }
477 |
478 expression IS NOT NULL_T
479 {
480 $$ = sdb_store_isnnull_matcher($1);
481 sdb_object_deref(SDB_OBJ($1));
482 }
483 |
484 expression IN expression
485 {
486 $$ = sdb_store_in_matcher($1, $3);
487 sdb_object_deref(SDB_OBJ($1));
488 sdb_object_deref(SDB_OBJ($3));
489 }
490 ;
492 expression:
493 '(' expression ')'
494 {
495 $$ = $2;
496 }
497 |
498 expression '+' expression
499 {
500 $$ = sdb_store_expr_create(SDB_DATA_ADD, $1, $3);
501 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
502 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
503 }
504 |
505 expression '-' expression
506 {
507 $$ = sdb_store_expr_create(SDB_DATA_SUB, $1, $3);
508 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
509 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
510 }
511 |
512 expression '*' expression
513 {
514 $$ = sdb_store_expr_create(SDB_DATA_MUL, $1, $3);
515 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
516 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
517 }
518 |
519 expression '/' expression
520 {
521 $$ = sdb_store_expr_create(SDB_DATA_DIV, $1, $3);
522 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
523 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
524 }
525 |
526 expression '%' expression
527 {
528 $$ = sdb_store_expr_create(SDB_DATA_MOD, $1, $3);
529 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
530 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
531 }
532 |
533 expression CONCAT expression
534 {
535 $$ = sdb_store_expr_create(SDB_DATA_CONCAT, $1, $3);
536 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
537 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
538 }
539 |
540 IDENTIFIER
541 {
542 int field;
543 /* TODO: this only works as long as queries
544 * are limited to hosts */
545 if (!strcasecmp($1, "host"))
546 field = SDB_FIELD_NAME;
547 else
548 field = sdb_store_parse_field_name($1);
549 free($1); $1 = NULL;
550 $$ = sdb_store_expr_fieldvalue(field);
551 }
552 |
553 IDENTIFIER '[' STRING ']'
554 {
555 if (strcasecmp($1, "attribute")) {
556 char errmsg[strlen($1) + strlen($3) + 32];
557 snprintf(errmsg, sizeof(errmsg),
558 YY_("unknown value %s[%s]"), $1, $3);
559 sdb_fe_yyerror(&yylloc, scanner, errmsg);
560 free($1); $1 = NULL;
561 free($3); $3 = NULL;
562 YYABORT;
563 }
564 $$ = sdb_store_expr_attrvalue($3);
565 free($1); $1 = NULL;
566 free($3); $3 = NULL;
567 }
568 |
569 data
570 {
571 $$ = sdb_store_expr_constvalue(&$1);
572 sdb_data_free_datum(&$1);
573 }
574 ;
576 cmp:
577 CMP_EQUAL { $$ = "="; }
578 |
579 CMP_NEQUAL { $$ = "!="; }
580 |
581 CMP_REGEX { $$ = "=~"; }
582 |
583 CMP_NREGEX { $$ = "!~"; }
584 |
585 CMP_LT { $$ = "<"; }
586 |
587 CMP_LE { $$ = "<="; }
588 |
589 CMP_GE { $$ = ">="; }
590 |
591 CMP_GT { $$ = ">"; }
592 ;
594 data:
595 STRING { $$.type = SDB_TYPE_STRING; $$.data.string = $1; }
596 |
597 INTEGER { $$ = $1; }
598 |
599 FLOAT { $$ = $1; }
600 |
601 datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; }
602 |
603 interval { $$ = $1; }
604 ;
606 datetime:
607 DATE TIME { $$ = $1 + $2; }
608 |
609 DATE { $$ = $1; }
610 |
611 TIME { $$ = $1; }
612 ;
614 interval:
615 interval interval_elem
616 {
617 $$.data.datetime = $1.data.datetime + $2.data.datetime;
618 }
619 |
620 interval_elem { $$ = $1; }
621 ;
623 interval_elem:
624 INTEGER IDENTIFIER
625 {
626 sdb_time_t unit = 1;
628 unit = sdb_strpunit($2);
629 if (! unit) {
630 char errmsg[strlen($2) + 32];
631 snprintf(errmsg, sizeof(errmsg),
632 YY_("invalid time unit %s"), $2);
633 sdb_fe_yyerror(&yylloc, scanner, errmsg);
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 static sdb_store_matcher_t *
659 name_iter_matcher(int m_type, const char *type_name, const char *cmp,
660 sdb_store_expr_t *expr)
661 {
662 int type = sdb_store_parse_object_type(type_name);
663 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op(cmp);
664 sdb_store_expr_t *e;
665 sdb_store_matcher_t *m, *tmp = NULL;
666 assert(cb);
668 /* TODO: this only works as long as queries
669 * are limited to hosts */
670 if (type == SDB_HOST) {
671 return NULL;
672 }
674 e = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
675 m = cb(e, expr);
676 if (m_type == MATCHER_ANY)
677 tmp = sdb_store_any_matcher(type, m);
678 else if (m_type == MATCHER_ALL)
679 tmp = sdb_store_all_matcher(type, m);
680 sdb_object_deref(SDB_OBJ(m));
681 sdb_object_deref(SDB_OBJ(e));
682 return tmp;
683 } /* name_iter_matcher */
685 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */