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