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 DAAP2SQL; 20 21options { 22 tokenVocab = DAAP; 23 ASTLabelType = pANTLR3_BASE_TREE; 24 language = C; 25} 26 27@header { 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <limits.h> 32 #include <errno.h> 33 34 #include "logger.h" 35 #include "db.h" 36 #include "daap_query.h" 37} 38 39@members { 40 struct dmap_query_field_map { 41 char *dmap_field; 42 char *db_col; 43 int as_int; 44 }; 45 46 /* gperf static hash, daap_query.gperf */ 47 #include "daap_query_hash.c" 48} 49 50query returns [ pANTLR3_STRING result ] 51@init { $result = NULL; } 52 : e = expr 53 { 54 if (!$e.valid) 55 { 56 $result = NULL; 57 } 58 else 59 { 60 $result = $e.result->factory->newRaw($e.result->factory); 61 $result->append8($result, "("); 62 $result->appendS($result, $e.result); 63 $result->append8($result, ")"); 64 } 65 } 66 ; 67 68expr returns [ pANTLR3_STRING result, int valid ] 69@init { $result = NULL; $valid = 1; } 70 : ^(OPAND a = expr b = expr) 71 { 72 if (!$a.valid || !$b.valid) 73 { 74 $valid = 0; 75 } 76 else 77 { 78 $result = $a.result->factory->newRaw($a.result->factory); 79 $result->append8($result, "("); 80 $result->appendS($result, $a.result); 81 $result->append8($result, " AND "); 82 $result->appendS($result, $b.result); 83 $result->append8($result, ")"); 84 } 85 } 86 | ^(OPOR a = expr b = expr) 87 { 88 if (!$a.valid || !$b.valid) 89 { 90 $valid = 0; 91 } 92 else 93 { 94 $result = $a.result->factory->newRaw($a.result->factory); 95 $result->append8($result, "("); 96 $result->appendS($result, $a.result); 97 $result->append8($result, " OR "); 98 $result->appendS($result, $b.result); 99 $result->append8($result, ")"); 100 } 101 } 102 | STR 103 { 104 pANTLR3_STRING str; 105 pANTLR3_UINT8 field; 106 pANTLR3_UINT8 val; 107 pANTLR3_UINT8 escaped; 108 ANTLR3_UINT8 op; 109 int neg_op; 110 const struct dmap_query_field_map *dqfm; 111 char *end; 112 long long llval; 113 114 escaped = NULL; 115 116 $result = $STR.text->factory->newRaw($STR.text->factory); 117 118 str = $STR.text->toUTF8($STR.text); 119 120 /* NOTE: the lexer delivers the string without quotes 121 which may not be obvious from the grammar due to embedded code 122 */ 123 124 /* Make daap.songalbumid:0 a no-op */ 125 if (strcmp((char *)str->chars, "daap.songalbumid:0") == 0) 126 { 127 $result->append8($result, "1 = 1"); 128 129 goto STR_out; 130 } 131 132 field = str->chars; 133 134 val = field; 135 while ((*val != '\0') && ((*val == '.') 136 || (*val == '-') 137 || ((*val >= 'a') && (*val <= 'z')) 138 || ((*val >= 'A') && (*val <= 'Z')) 139 || ((*val >= '0') && (*val <= '9')))) 140 { 141 val++; 142 } 143 144 if (*field == '\0') 145 { 146 DPRINTF(E_LOG, L_DAAP, "No field name found in clause '\%s'\n", field); 147 $valid = 0; 148 goto STR_result_valid_0; /* ABORT */ 149 } 150 151 if (*val == '\0') 152 { 153 DPRINTF(E_LOG, L_DAAP, "No operator found in clause '\%s'\n", field); 154 $valid = 0; 155 goto STR_result_valid_0; /* ABORT */ 156 } 157 158 op = *val; 159 *val = '\0'; 160 val++; 161 162 if (op == '!') 163 { 164 if (*val == '\0') 165 { 166 DPRINTF(E_LOG, L_DAAP, "Negation found but operator missing in clause '\%s\%c'\n", field, op); 167 $valid = 0; 168 goto STR_result_valid_0; /* ABORT */ 169 } 170 171 neg_op = 1; 172 op = *val; 173 val++; 174 } 175 else 176 neg_op = 0; 177 178 /* Lookup DMAP field in the query field map */ 179 dqfm = daap_query_field_lookup((char *)field, strlen((char *)field)); 180 if (!dqfm) 181 { 182 DPRINTF(E_LOG, L_DAAP, "DMAP field '\%s' is not a valid field in queries\n", field); 183 $valid = 0; 184 goto STR_result_valid_0; /* ABORT */ 185 } 186 187 /* Empty values OK for string fields, NOK for integer */ 188 if (*val == '\0') 189 { 190 if (dqfm->as_int) 191 { 192 DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s\%s\%c'\n", field, (neg_op) ? "!" : "", op); 193 $valid = 0; 194 goto STR_result_valid_0; /* ABORT */ 195 } 196 197 /* Need to check against NULL too */ 198 if (op == ':') 199 $result->append8($result, "("); 200 } 201 202 $result->append8($result, dqfm->db_col); 203 204 /* Int field: check integer conversion */ 205 if (dqfm->as_int) 206 { 207 errno = 0; 208 llval = strtoll((const char *)val, &end, 10); 209 210 if (((errno == ERANGE) && ((llval == LLONG_MAX) || (llval == LLONG_MIN))) 211 || ((errno != 0) && (llval == 0))) 212 { 213 DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not convert to an integer type\n", 214 val, field, (neg_op) ? "!" : "", op, val); 215 $valid = 0; 216 goto STR_result_valid_0; /* ABORT */ 217 } 218 219 if (end == (char *)val) 220 { 221 DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%s\%c\%s' does not represent an integer value\n", 222 val, field, (neg_op) ? "!" : "", op, val); 223 $valid = 0; 224 goto STR_result_valid_0; /* ABORT */ 225 } 226 227 *end = '\0'; /* Cut out potential garbage - we're being kind */ 228 } 229 /* String field: escape string, check for '*' */ 230 else 231 { 232 if (op != ':') 233 { 234 DPRINTF(E_LOG, L_DAAP, "Operation '\%c' not valid for string values\n", op); 235 $valid = 0; 236 goto STR_result_valid_0; /* ABORT */ 237 } 238 239 escaped = (pANTLR3_UINT8)db_escape_string((char *)val); 240 if (!escaped) 241 { 242 DPRINTF(E_LOG, L_DAAP, "Could not escape value\n"); 243 $valid = 0; 244 goto STR_result_valid_0; /* ABORT */ 245 } 246 247 val = escaped; 248 249 if (val[0] == '*') 250 { 251 op = '\%'; 252 val[0] = '\%'; 253 } 254 255 if (val[strlen((char *)val) - 1] == '*') 256 { 257 op = '\%'; 258 val[strlen((char *)val) - 1] = '\%'; 259 } 260 } 261 262 switch(op) 263 { 264 case ':': 265 if (neg_op) 266 $result->append8($result, " <> "); 267 else 268 $result->append8($result, " = "); 269 break; 270 271 case '+': 272 if (neg_op) 273 $result->append8($result, " <= "); 274 else 275 $result->append8($result, " > "); 276 break; 277 278 case '-': 279 if (neg_op) 280 $result->append8($result, " >= "); 281 else 282 $result->append8($result, " < "); 283 break; 284 285 case '\%': 286 $result->append8($result, " LIKE "); 287 break; 288 289 default: 290 if (neg_op) 291 DPRINTF(E_LOG, L_DAAP, "Missing or unknown operator '\%c' in clause '\%s!\%c\%s'\n", op, field, op, val); 292 else 293 DPRINTF(E_LOG, L_DAAP, "Unknown operator '\%c' in clause '\%s\%c\%s'\n", op, field, op, val); 294 $valid = 0; 295 goto STR_result_valid_0; /* ABORT */ 296 break; 297 } 298 299 if (!dqfm->as_int) 300 $result->append8($result, "'"); 301 302 $result->append8($result, (const char *)val); 303 304 if (!dqfm->as_int) 305 $result->append8($result, "'"); 306 307 /* For empty string value, we need to check against NULL too */ 308 if ((*val == '\0') && (op == ':')) 309 { 310 if (neg_op) 311 $result->append8($result, " AND "); 312 else 313 $result->append8($result, " OR "); 314 315 $result->append8($result, dqfm->db_col); 316 317 if (neg_op) 318 $result->append8($result, " IS NOT NULL"); 319 else 320 $result->append8($result, " IS NULL"); 321 322 $result->append8($result, ")"); 323 } 324 325 STR_result_valid_0: /* bail out label */ 326 ; 327 328 if (escaped) 329 free(escaped); 330 331 STR_out: /* get out of here */ 332 ; 333 } 334 ; 335