1/* 2 * Copyright (C) 2009-2011 Julien BLACHE <jb@jblache.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 17 */ 18 19tree grammar RSP2SQL; 20 21options { 22 tokenVocab = RSP; 23 ASTLabelType = pANTLR3_BASE_TREE; 24 language = C; 25} 26 27@header { 28 /* Needs #define _GNU_SOURCE for strptime() */ 29 30 #include <stdio.h> 31 #include <string.h> 32 #include <time.h> 33 #include <stdint.h> 34 35 #include "logger.h" 36 #include "db.h" 37 #include "misc.h" 38 #include "rsp_query.h" 39} 40 41@members { 42 #define RSP_TYPE_STRING 0 43 #define RSP_TYPE_INT 1 44 #define RSP_TYPE_DATE 2 45 46 struct rsp_query_field_map { 47 char *rsp_field; 48 int field_type; 49 /* RSP fields are named after the DB columns - or vice versa */ 50 }; 51 52 /* gperf static hash, rsp_query.gperf */ 53 #include "rsp_query_hash.c" 54} 55 56query returns [ pANTLR3_STRING result ] 57@init { $result = NULL; } 58 : e = expr 59 { 60 if (!$e.valid) 61 { 62 $result = NULL; 63 } 64 else 65 { 66 $result = $e.result->factory->newRaw($e.result->factory); 67 $result->append8($result, "("); 68 $result->appendS($result, $e.result); 69 $result->append8($result, ")"); 70 } 71 } 72 ; 73 74expr returns [ pANTLR3_STRING result, int valid ] 75@init { $result = NULL; $valid = 1; } 76 : ^(AND a = expr b = expr) 77 { 78 if (!$a.valid || !$b.valid) 79 { 80 $valid = 0; 81 } 82 else 83 { 84 $result = $a.result->factory->newRaw($a.result->factory); 85 $result->append8($result, "("); 86 $result->appendS($result, $a.result); 87 $result->append8($result, " AND "); 88 $result->appendS($result, $b.result); 89 $result->append8($result, ")"); 90 } 91 } 92 | ^(OR a = expr b = expr) 93 { 94 if (!$a.valid || !$b.valid) 95 { 96 $valid = 0; 97 } 98 else 99 { 100 $result = $a.result->factory->newRaw($a.result->factory); 101 $result->append8($result, "("); 102 $result->appendS($result, $a.result); 103 $result->append8($result, " OR "); 104 $result->appendS($result, $b.result); 105 $result->append8($result, ")"); 106 } 107 } 108 | c = strcrit 109 { 110 $valid = $c.valid; 111 $result = $c.result; 112 } 113 | ^(NOT c = strcrit) 114 { 115 if (!$c.valid) 116 { 117 $valid = 0; 118 } 119 else 120 { 121 $result = $c.result->factory->newRaw($c.result->factory); 122 $result->append8($result, "(NOT "); 123 $result->appendS($result, $c.result); 124 $result->append8($result, ")"); 125 } 126 } 127 | i = intcrit 128 { 129 $valid = $i.valid; 130 $result = $i.result; 131 } 132 | ^(NOT i = intcrit) 133 { 134 if (!$i.valid) 135 { 136 $valid = 0; 137 } 138 else 139 { 140 $result = $i.result->factory->newRaw($i.result->factory); 141 $result->append8($result, "(NOT "); 142 $result->appendS($result, $i.result); 143 $result->append8($result, ")"); 144 } 145 } 146 | d = datecrit 147 { 148 $valid = $d.valid; 149 $result = $d.result; 150 } 151 ; 152 153strcrit returns [ pANTLR3_STRING result, int valid ] 154@init { $result = NULL; $valid = 1; } 155 : ^(o = strop f = FIELD s = STR) 156 { 157 char *op; 158 const struct rsp_query_field_map *rqfp; 159 pANTLR3_STRING field; 160 char *escaped; 161 ANTLR3_UINT32 optok; 162 163 escaped = NULL; 164 165 op = NULL; 166 optok = $o.op->getType($o.op); 167 switch (optok) 168 { 169 case EQUAL: 170 op = " = "; 171 break; 172 173 case INCLUDES: 174 case STARTSW: 175 case ENDSW: 176 op = " LIKE "; 177 break; 178 } 179 180 field = $f->getText($f); 181 182 /* Field lookup */ 183 rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars)); 184 if (!rqfp) 185 { 186 DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars); 187 $valid = 0; 188 goto strcrit_valid_0; /* ABORT */ 189 } 190 191 /* Check field type */ 192 if (rqfp->field_type != RSP_TYPE_STRING) 193 { 194 DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a string field\n", field->chars); 195 $valid = 0; 196 goto strcrit_valid_0; /* ABORT */ 197 } 198 199 escaped = db_escape_string((char *)$s->getText($s)->chars); 200 if (!escaped) 201 { 202 DPRINTF(E_LOG, L_RSP, "Could not escape value\n"); 203 $valid = 0; 204 goto strcrit_valid_0; /* ABORT */ 205 } 206 207 $result = field->factory->newRaw(field->factory); 208 $result->append8($result, "f."); 209 $result->appendS($result, field); 210 $result->append8($result, op); 211 $result->append8($result, "'"); 212 if ((optok == INCLUDES) || (optok == STARTSW)) 213 $result->append8($result, "\%"); 214 215 $result->append8($result, escaped); 216 217 if ((optok == INCLUDES) || (optok == ENDSW)) 218 $result->append8($result, "\%"); 219 $result->append8($result, "'"); 220 221 strcrit_valid_0: 222 ; 223 224 if (escaped) 225 free(escaped); 226 } 227 ; 228 229strop returns [ pANTLR3_COMMON_TOKEN op ] 230@init { $op = NULL; } 231 : n = EQUAL 232 { $op = $n->getToken($n); } 233 | n = INCLUDES 234 { $op = $n->getToken($n); } 235 | n = STARTSW 236 { $op = $n->getToken($n); } 237 | n = ENDSW 238 { $op = $n->getToken($n); } 239 ; 240 241intcrit returns [ pANTLR3_STRING result, int valid ] 242@init { $result = NULL; $valid = 1; } 243 : ^(o = intop f = FIELD i = INT) 244 { 245 char *op; 246 const struct rsp_query_field_map *rqfp; 247 pANTLR3_STRING field; 248 249 op = NULL; 250 switch ($o.op->getType($o.op)) 251 { 252 case EQUAL: 253 op = " = "; 254 break; 255 256 case LESS: 257 op = " < "; 258 break; 259 260 case GREATER: 261 op = " > "; 262 break; 263 264 case LTE: 265 op = " <= "; 266 break; 267 268 case GTE: 269 op = " >= "; 270 break; 271 } 272 273 field = $f->getText($f); 274 275 /* Field lookup */ 276 rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars)); 277 if (!rqfp) 278 { 279 DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars); 280 $valid = 0; 281 goto intcrit_valid_0; /* ABORT */ 282 } 283 284 /* Check field type */ 285 if (rqfp->field_type != RSP_TYPE_INT) 286 { 287 DPRINTF(E_LOG, L_RSP, "Field '\%s' is not an integer field\n", field->chars); 288 $valid = 0; 289 goto intcrit_valid_0; /* ABORT */ 290 } 291 292 $result = field->factory->newRaw(field->factory); 293 $result->append8($result, "f."); 294 $result->appendS($result, field); 295 $result->append8($result, op); 296 $result->appendS($result, $i->getText($i)); 297 298 intcrit_valid_0: 299 ; 300 } 301 ; 302 303intop returns [ pANTLR3_COMMON_TOKEN op ] 304@init { $op = NULL; } 305 : n = EQUAL 306 { $op = $n->getToken($n); } 307 | n = LESS 308 { $op = $n->getToken($n); } 309 | n = GREATER 310 { $op = $n->getToken($n); } 311 | n = LTE 312 { $op = $n->getToken($n); } 313 | n = GTE 314 { $op = $n->getToken($n); } 315 ; 316 317datecrit returns [ pANTLR3_STRING result, int valid ] 318@init { $result = NULL; $valid = 1; } 319 : ^(o = dateop f = FIELD d = datespec) 320 { 321 char *op; 322 const struct rsp_query_field_map *rqfp; 323 pANTLR3_STRING field; 324 char buf[32]; 325 int ret; 326 327 op = NULL; 328 switch ($o.op->getType($o.op)) 329 { 330 case BEFORE: 331 op = " < "; 332 break; 333 334 case AFTER: 335 op = " > "; 336 break; 337 } 338 339 field = $f->getText($f); 340 341 /* Field lookup */ 342 rqfp = rsp_query_field_lookup((char *)field->chars, strlen((char *)field->chars)); 343 if (!rqfp) 344 { 345 DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars); 346 $valid = 0; 347 goto datecrit_valid_0; /* ABORT */ 348 } 349 350 /* Check field type */ 351 if (rqfp->field_type != RSP_TYPE_DATE) 352 { 353 DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a date field\n", field->chars); 354 $valid = 0; 355 goto datecrit_valid_0; /* ABORT */ 356 } 357 358 ret = snprintf(buf, sizeof(buf), "\%ld", $d.date); 359 if ((ret < 0) || (ret >= sizeof(buf))) 360 { 361 DPRINTF(E_LOG, L_RSP, "Date \%ld too large for buffer, oops!\n", $d.date); 362 $valid = 0; 363 goto datecrit_valid_0; /* ABORT */ 364 } 365 366 $result = field->factory->newRaw(field->factory); 367 $result->append8($result, "f."); 368 $result->appendS($result, field); 369 $result->append8($result, op); 370 $result->append8($result, buf); 371 372 datecrit_valid_0: 373 ; 374 } 375 ; 376 377dateop returns [ pANTLR3_COMMON_TOKEN op ] 378@init { $op = NULL; } 379 : n = BEFORE 380 { $op = $n->getToken($n); } 381 | n = AFTER 382 { $op = $n->getToken($n); } 383 ; 384 385datespec returns [ time_t date, int valid ] 386@init { $date = 0; $valid = 1; } 387 : r = dateref 388 { 389 if (!$r.valid) 390 $valid = 0; 391 else 392 $date = $r.date; 393 } 394 | ^(o = dateop r = dateref m = INT i = dateintval) 395 { 396 int32_t val; 397 int ret; 398 399 if (!$r.valid || !$i.valid) 400 { 401 $valid = 0; 402 goto datespec_valid_0; /* ABORT */ 403 } 404 405 ret = safe_atoi32((char *)$m->getText($m)->chars, &val); 406 if (ret < 0) 407 { 408 DPRINTF(E_LOG, L_RSP, "Could not convert '\%s' to integer\n", (char *)$m->getText($m)); 409 $valid = 0; 410 goto datespec_valid_0; /* ABORT */ 411 } 412 413 switch ($o.op->getType($o.op)) 414 { 415 case BEFORE: 416 $date = $r.date - (val * $i.period); 417 break; 418 419 case AFTER: 420 $date = $r.date + (val * $i.period); 421 break; 422 } 423 424 datespec_valid_0: 425 ; 426 } 427 ; 428 429dateref returns [ time_t date, int valid ] 430@init { $date = 0; $valid = 1; } 431 : n = DATE 432 { 433 struct tm tm; 434 char *ret; 435 436 ret = strptime((char *)$n->getText($n), "\%Y-\%m-\%d", &tm); 437 if (!ret) 438 { 439 DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be interpreted\n", (char *)$n->getText($n)); 440 $valid = 0; 441 goto dateref_valid_0; /* ABORT */ 442 } 443 else 444 { 445 if (*ret != '\0') 446 DPRINTF(E_LOG, L_RSP, "Garbage at end of date '\%s' ?!\n", (char *)$n->getText($n)); 447 448 $date = mktime(&tm); 449 if ($date == (time_t) -1) 450 { 451 DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be converted to an epoch\n", (char *)$n->getText($n)); 452 $valid = 0; 453 goto dateref_valid_0; /* ABORT */ 454 } 455 } 456 457 dateref_valid_0: 458 ; 459 } 460 | TODAY 461 { $date = time(NULL); } 462 ; 463 464dateintval returns [ time_t period, int valid ] 465@init { $period = 0; $valid = 1; } 466 : DAY 467 { $period = 24 * 60 * 60; } 468 | WEEK 469 { $period = 7 * 24 * 60 * 60; } 470 | MONTH 471 { $period = 30 * 24 * 60 * 60; } 472 | YEAR 473 { $period = 365 * 24 * 60 * 60; } 474 ; 475