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