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 int
47 sdb_fe_yylex(YYSTYPE *yylval, YYLTYPE *yylloc, sdb_fe_yyscan_t yyscanner);
49 sdb_fe_yyextra_t *
50 sdb_fe_yyget_extra(sdb_fe_yyscan_t scanner);
52 void
53 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg);
55 /* quick access to the current parse tree */
56 #define pt sdb_fe_yyget_extra(scanner)->parsetree
58 /* quick access to the parser mode */
59 #define parser_mode sdb_fe_yyget_extra(scanner)->mode
61 #define MODE_TO_STRING(m) \
62 (((m) == SDB_PARSE_DEFAULT) ? "statement" \
63 : ((m) == SDB_PARSE_COND) ? "condition" \
64 : ((m) == SDB_PARSE_EXPR) ? "expression" \
65 : "UNKNOWN")
67 %}
69 %pure-parser
70 %lex-param {sdb_fe_yyscan_t scanner}
71 %parse-param {sdb_fe_yyscan_t scanner}
72 %locations
73 %error-verbose
74 %expect 0
75 %name-prefix "sdb_fe_yy"
77 %union {
78 const char *sstr; /* static string */
79 char *str;
81 sdb_data_t data;
82 sdb_time_t datetime;
84 sdb_llist_t *list;
85 sdb_conn_node_t *node;
87 sdb_store_matcher_t *m;
88 sdb_store_expr_t *expr;
89 }
91 %start statements
93 %token SCANNER_ERROR
95 %token AND OR IS NOT MATCHING FILTER
96 %token CMP_EQUAL CMP_NEQUAL CMP_REGEX CMP_NREGEX
97 %token CMP_LT CMP_LE CMP_GE CMP_GT IN
98 %token CONCAT
100 %token START END
102 /* NULL token */
103 %token NULL_T
105 %token FETCH LIST LOOKUP TIMESERIES
107 %token <str> IDENTIFIER STRING
109 %token <data> INTEGER FLOAT
111 %token <datetime> DATE TIME
113 /* Precedence (lowest first): */
114 %left OR
115 %left AND
116 %right NOT
117 %left CMP_EQUAL CMP_NEQUAL
118 %left CMP_LT CMP_LE CMP_GE CMP_GT
119 %nonassoc CMP_REGEX CMP_NREGEX
120 %nonassoc IN
121 %left CONCAT
122 %nonassoc IS
123 %left '+' '-'
124 %left '*' '/' '%'
125 %left '[' ']'
126 %left '(' ')'
127 %left '.'
129 %type <list> statements
130 %type <node> statement
131 fetch_statement
132 list_statement
133 lookup_statement
134 timeseries_statement
135 matching_clause
136 filter_clause
137 condition
139 %type <m> matcher
140 compare_matcher
142 %type <expr> expression
144 %type <sstr> cmp
146 %type <data> data
147 interval interval_elem
149 %type <datetime> datetime
150 start_clause end_clause
152 %destructor { free($$); } <str>
153 %destructor { sdb_object_deref(SDB_OBJ($$)); } <node> <m> <expr>
154 %destructor { sdb_data_free_datum(&$$); } <data>
156 %%
158 statements:
159 statements ';' statement
160 {
161 /* only accepted in default parse mode */
162 if (parser_mode != SDB_PARSE_DEFAULT) {
163 char errmsg[1024];
164 snprintf(errmsg, sizeof(errmsg),
165 YY_("syntax error, unexpected statement, "
166 "expecting %s"), MODE_TO_STRING(parser_mode));
167 sdb_fe_yyerror(&yylloc, scanner, errmsg);
168 sdb_object_deref(SDB_OBJ($3));
169 YYABORT;
170 }
172 if ($3) {
173 sdb_llist_append(pt, SDB_OBJ($3));
174 sdb_object_deref(SDB_OBJ($3));
175 }
176 }
177 |
178 statement
179 {
180 /* only accepted in default parse mode */
181 if (parser_mode != SDB_PARSE_DEFAULT) {
182 char errmsg[1024];
183 snprintf(errmsg, sizeof(errmsg),
184 YY_("syntax error, unexpected statement, "
185 "expecting %s"), MODE_TO_STRING(parser_mode));
186 sdb_fe_yyerror(&yylloc, scanner, errmsg);
187 sdb_object_deref(SDB_OBJ($1));
188 YYABORT;
189 }
191 if ($1) {
192 sdb_llist_append(pt, SDB_OBJ($1));
193 sdb_object_deref(SDB_OBJ($1));
194 }
195 }
196 |
197 condition
198 {
199 /* only accepted in condition parse mode */
200 if (! (parser_mode & SDB_PARSE_COND)) {
201 char errmsg[1024];
202 snprintf(errmsg, sizeof(errmsg),
203 YY_("syntax error, unexpected condition, "
204 "expecting %s"), MODE_TO_STRING(parser_mode));
205 sdb_fe_yyerror(&yylloc, scanner, errmsg);
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 expression
217 {
218 /* only accepted in expression parse mode */
219 if (! (parser_mode & SDB_PARSE_EXPR)) {
220 char errmsg[1024];
221 snprintf(errmsg, sizeof(errmsg),
222 YY_("syntax error, unexpected expression, "
223 "expecting %s"), MODE_TO_STRING(parser_mode));
224 sdb_fe_yyerror(&yylloc, scanner, errmsg);
225 sdb_object_deref(SDB_OBJ($1));
226 YYABORT;
227 }
229 if ($1) {
230 sdb_conn_node_t *n;
231 n = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
232 conn_expr_t, conn_expr_destroy));
233 n->cmd = CONNECTION_EXPR;
234 CONN_EXPR(n)->expr = $1;
236 sdb_llist_append(pt, SDB_OBJ(n));
237 sdb_object_deref(SDB_OBJ(n));
238 }
239 }
240 ;
242 statement:
243 fetch_statement
244 |
245 list_statement
246 |
247 lookup_statement
248 |
249 timeseries_statement
250 |
251 /* empty */
252 {
253 $$ = NULL;
254 }
255 ;
257 /*
258 * FETCH <type> <hostname> [FILTER <condition>];
259 *
260 * Retrieve detailed information about a single host.
261 */
262 fetch_statement:
263 FETCH IDENTIFIER STRING filter_clause
264 {
265 /* TODO: support other types as well */
266 if (strcasecmp($2, "host")) {
267 char errmsg[strlen($2) + 32];
268 snprintf(errmsg, sizeof(errmsg),
269 YY_("unknown data-source %s"), $2);
270 sdb_fe_yyerror(&yylloc, scanner, errmsg);
271 free($2); $2 = NULL;
272 free($3); $3 = NULL;
273 sdb_object_deref(SDB_OBJ($4));
274 YYABORT;
275 }
277 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
278 conn_fetch_t, conn_fetch_destroy));
279 CONN_FETCH($$)->type = SDB_HOST;
280 CONN_FETCH($$)->name = $3;
281 CONN_FETCH($$)->filter = CONN_MATCHER($4);
282 $$->cmd = CONNECTION_FETCH;
283 free($2); $2 = NULL;
284 }
285 ;
287 /*
288 * LIST <type> [FILTER <condition>];
289 *
290 * Returns a list of all hosts in the store.
291 */
292 list_statement:
293 LIST IDENTIFIER filter_clause
294 {
295 int type = sdb_store_parse_object_type_plural($2);
296 if ((type < 0) || (type == SDB_ATTRIBUTE)) {
297 char errmsg[strlen($2) + 32];
298 snprintf(errmsg, sizeof(errmsg),
299 YY_("unknown data-source %s"), $2);
300 sdb_fe_yyerror(&yylloc, scanner, errmsg);
301 free($2); $2 = NULL;
302 sdb_object_deref(SDB_OBJ($3));
303 YYABORT;
304 }
306 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
307 conn_list_t, conn_list_destroy));
308 CONN_LIST($$)->type = type;
309 CONN_LIST($$)->filter = CONN_MATCHER($3);
310 $$->cmd = CONNECTION_LIST;
311 free($2); $2 = NULL;
312 }
313 ;
315 /*
316 * LOOKUP <type> MATCHING <condition> [FILTER <condition>];
317 *
318 * Returns detailed information about <type> matching condition.
319 */
320 lookup_statement:
321 LOOKUP IDENTIFIER matching_clause filter_clause
322 {
323 /* TODO: support other types as well */
324 if (strcasecmp($2, "hosts")) {
325 char errmsg[strlen($2) + 32];
326 snprintf(errmsg, sizeof(errmsg),
327 YY_("unknown data-source %s"), $2);
328 sdb_fe_yyerror(&yylloc, scanner, errmsg);
329 free($2); $2 = NULL;
330 sdb_object_deref(SDB_OBJ($3));
331 sdb_object_deref(SDB_OBJ($4));
332 YYABORT;
333 }
335 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
336 conn_lookup_t, conn_lookup_destroy));
337 CONN_LOOKUP($$)->type = SDB_HOST;
338 CONN_LOOKUP($$)->matcher = CONN_MATCHER($3);
339 CONN_LOOKUP($$)->filter = CONN_MATCHER($4);
340 $$->cmd = CONNECTION_LOOKUP;
341 free($2); $2 = NULL;
342 }
343 ;
345 matching_clause:
346 MATCHING condition { $$ = $2; }
347 |
348 /* empty */ { $$ = NULL; }
350 filter_clause:
351 FILTER condition { $$ = $2; }
352 |
353 /* empty */ { $$ = NULL; }
355 /*
356 * TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>];
357 *
358 * Returns a time-series for the specified host's metric.
359 */
360 timeseries_statement:
361 TIMESERIES STRING '.' STRING start_clause end_clause
362 {
363 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
364 conn_ts_t, conn_ts_destroy));
365 CONN_TS($$)->hostname = $2;
366 CONN_TS($$)->metric = $4;
367 CONN_TS($$)->opts.start = $5;
368 CONN_TS($$)->opts.end = $6;
369 $$->cmd = CONNECTION_TIMESERIES;
370 }
371 ;
373 start_clause:
374 START datetime { $$ = $2; }
375 |
376 /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; }
378 end_clause:
379 END datetime { $$ = $2; }
380 |
381 /* empty */ { $$ = sdb_gettime(); }
383 /*
384 * Basic expressions.
385 */
387 condition:
388 matcher
389 {
390 if (! $1) {
391 /* TODO: improve error reporting */
392 sdb_fe_yyerror(&yylloc, scanner,
393 YY_("syntax error, invalid condition"));
394 YYABORT;
395 }
397 $$ = SDB_CONN_NODE(sdb_object_create_dT(/* name = */ NULL,
398 conn_matcher_t, conn_matcher_destroy));
399 $$->cmd = CONNECTION_MATCHER;
400 CONN_MATCHER($$)->matcher = $1;
401 }
402 ;
404 matcher:
405 '(' matcher ')'
406 {
407 $$ = $2;
408 }
409 |
410 matcher AND matcher
411 {
412 $$ = sdb_store_con_matcher($1, $3);
413 sdb_object_deref(SDB_OBJ($1));
414 sdb_object_deref(SDB_OBJ($3));
415 }
416 |
417 matcher OR matcher
418 {
419 $$ = sdb_store_dis_matcher($1, $3);
420 sdb_object_deref(SDB_OBJ($1));
421 sdb_object_deref(SDB_OBJ($3));
422 }
423 |
424 NOT matcher
425 {
426 $$ = sdb_store_inv_matcher($2);
427 sdb_object_deref(SDB_OBJ($2));
428 }
429 |
430 compare_matcher
431 {
432 $$ = $1;
433 }
434 ;
436 compare_matcher:
437 expression cmp expression
438 {
439 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op($2);
440 assert(cb); /* else, the grammar accepts invalid 'cmp' */
441 $$ = cb($1, $3);
442 sdb_object_deref(SDB_OBJ($1));
443 sdb_object_deref(SDB_OBJ($3));
444 }
445 |
446 IDENTIFIER cmp expression
447 {
448 int type = sdb_store_parse_object_type($1);
449 sdb_store_expr_t *e = sdb_store_expr_fieldvalue(SDB_FIELD_NAME);
450 sdb_store_matcher_op_cb cb = sdb_store_parse_matcher_op($2);
451 sdb_store_matcher_t *m;
452 assert(cb);
454 m = cb(e, $3);
455 /* TODO: this only works as long as queries
456 * are limited to hosts */
457 if (type == SDB_HOST) {
458 $$ = m;
459 }
460 else {
461 $$ = sdb_store_child_matcher(type, m);
462 sdb_object_deref(SDB_OBJ(m));
463 }
465 free($1); $1 = NULL;
466 sdb_object_deref(SDB_OBJ($3));
467 sdb_object_deref(SDB_OBJ(e));
468 }
469 |
470 expression IS NULL_T
471 {
472 $$ = sdb_store_isnull_matcher($1);
473 sdb_object_deref(SDB_OBJ($1));
474 }
475 |
476 expression IS NOT NULL_T
477 {
478 $$ = sdb_store_isnnull_matcher($1);
479 sdb_object_deref(SDB_OBJ($1));
480 }
481 |
482 expression IN expression
483 {
484 $$ = sdb_store_in_matcher($1, $3);
485 sdb_object_deref(SDB_OBJ($1));
486 sdb_object_deref(SDB_OBJ($3));
487 }
488 ;
490 expression:
491 '(' expression ')'
492 {
493 $$ = $2;
494 }
495 |
496 expression '+' expression
497 {
498 $$ = sdb_store_expr_create(SDB_DATA_ADD, $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_SUB, $1, $3);
506 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
507 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
508 }
509 |
510 expression '*' expression
511 {
512 $$ = sdb_store_expr_create(SDB_DATA_MUL, $1, $3);
513 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
514 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
515 }
516 |
517 expression '/' expression
518 {
519 $$ = sdb_store_expr_create(SDB_DATA_DIV, $1, $3);
520 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
521 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
522 }
523 |
524 expression '%' expression
525 {
526 $$ = sdb_store_expr_create(SDB_DATA_MOD, $1, $3);
527 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
528 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
529 }
530 |
531 expression CONCAT expression
532 {
533 $$ = sdb_store_expr_create(SDB_DATA_CONCAT, $1, $3);
534 sdb_object_deref(SDB_OBJ($1)); $1 = NULL;
535 sdb_object_deref(SDB_OBJ($3)); $3 = NULL;
536 }
537 |
538 '.' IDENTIFIER
539 {
540 int field = sdb_store_parse_field_name($2);
541 free($2); $2 = NULL;
542 $$ = sdb_store_expr_fieldvalue(field);
543 }
544 |
545 IDENTIFIER '[' STRING ']'
546 {
547 if (strcasecmp($1, "attribute")) {
548 char errmsg[strlen($1) + strlen($3) + 32];
549 snprintf(errmsg, sizeof(errmsg),
550 YY_("unknown value %s[%s]"), $1, $3);
551 sdb_fe_yyerror(&yylloc, scanner, errmsg);
552 free($1); $1 = NULL;
553 free($3); $3 = NULL;
554 YYABORT;
555 }
556 $$ = sdb_store_expr_attrvalue($3);
557 free($1); $1 = NULL;
558 free($3); $3 = NULL;
559 }
560 |
561 data
562 {
563 $$ = sdb_store_expr_constvalue(&$1);
564 sdb_data_free_datum(&$1);
565 }
566 ;
568 cmp:
569 CMP_EQUAL { $$ = "="; }
570 |
571 CMP_NEQUAL { $$ = "!="; }
572 |
573 CMP_REGEX { $$ = "=~"; }
574 |
575 CMP_NREGEX { $$ = "!~"; }
576 |
577 CMP_LT { $$ = "<"; }
578 |
579 CMP_LE { $$ = "<="; }
580 |
581 CMP_GE { $$ = ">="; }
582 |
583 CMP_GT { $$ = ">"; }
584 ;
586 data:
587 STRING { $$.type = SDB_TYPE_STRING; $$.data.string = $1; }
588 |
589 INTEGER { $$ = $1; }
590 |
591 FLOAT { $$ = $1; }
592 |
593 datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; }
594 |
595 interval { $$ = $1; }
596 ;
598 datetime:
599 DATE TIME { $$ = $1 + $2; }
600 |
601 DATE { $$ = $1; }
602 |
603 TIME { $$ = $1; }
604 ;
606 interval:
607 interval interval_elem
608 {
609 $$.data.datetime = $1.data.datetime + $2.data.datetime;
610 }
611 |
612 interval_elem { $$ = $1; }
613 ;
615 interval_elem:
616 INTEGER IDENTIFIER
617 {
618 sdb_time_t unit = 1;
620 unit = sdb_strpunit($2);
621 if (! unit) {
622 char errmsg[strlen($2) + 32];
623 snprintf(errmsg, sizeof(errmsg),
624 YY_("invalid time unit %s"), $2);
625 sdb_fe_yyerror(&yylloc, scanner, errmsg);
626 free($2); $2 = NULL;
627 YYABORT;
628 }
629 free($2); $2 = NULL;
631 $$.type = SDB_TYPE_DATETIME;
632 $$.data.datetime = (sdb_time_t)$1.data.integer * unit;
634 if ($1.data.integer < 0) {
635 sdb_fe_yyerror(&yylloc, scanner,
636 YY_("syntax error, negative intervals not supported"));
637 YYABORT;
638 }
639 }
640 ;
642 %%
644 void
645 sdb_fe_yyerror(YYLTYPE *lval, sdb_fe_yyscan_t scanner, const char *msg)
646 {
647 sdb_log(SDB_LOG_ERR, "frontend: parse error: %s", msg);
648 } /* sdb_fe_yyerror */
650 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */