owntone-server/src/RSP2SQL.g

452 lines
9.3 KiB
Plaintext

/*
* Copyright (C) 2009 Julien BLACHE <jb@jblache.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
tree grammar RSP2SQL;
options {
tokenVocab = RSP;
ASTLabelType = pANTLR3_BASE_TREE;
language = C;
}
@header {
/* Needs #define _GNU_SOURCE for strptime() */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "logger.h"
#include "db.h"
#include "misc.h"
#include "rsp_query.h"
}
query returns [ pANTLR3_STRING result ]
: e = expr
{
if (!$e.valid)
{
$result = NULL;
}
else
{
$result = $e.result->factory->newRaw($e.result->factory);
$result->append8($result, "(");
$result->appendS($result, $e.result);
$result->append8($result, ")");
}
}
;
expr returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(AND a = expr b = expr)
{
if (!$a.valid || !$b.valid)
{
$valid = 0;
}
else
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " AND ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
}
| ^(OR a = expr b = expr)
{
if (!$a.valid || !$b.valid)
{
$valid = 0;
}
else
{
$result = $a.result->factory->newRaw($a.result->factory);
$result->append8($result, "(");
$result->appendS($result, $a.result);
$result->append8($result, " OR ");
$result->appendS($result, $b.result);
$result->append8($result, ")");
}
}
| c = strcrit
{
$valid = $c.valid;
$result = $c.result;
}
| ^(NOT c = strcrit)
{
if (!$c.valid)
{
$valid = 0;
}
else
{
$result = $c.result->factory->newRaw($c.result->factory);
$result->append8($result, "(NOT ");
$result->appendS($result, $c.result);
$result->append8($result, ")");
}
}
| i = intcrit
{
$valid = $i.valid;
$result = $i.result;
}
| ^(NOT i = intcrit)
{
if (!$i.valid)
{
$valid = 0;
}
else
{
$result = $i.result->factory->newRaw($i.result->factory);
$result->append8($result, "(NOT ");
$result->appendS($result, $i.result);
$result->append8($result, ")");
}
}
| d = datecrit
{
$valid = $d.valid;
$result = $d.result;
}
;
strcrit returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(o = strop f = FIELD s = STR)
{
char *op;
struct rsp_query_field_map *rqfp;
pANTLR3_STRING field;
char *escaped;
ANTLR3_UINT32 optok;
escaped = NULL;
op = NULL;
optok = $o.op->getType($o.op);
switch (optok)
{
case EQUAL:
op = " = ";
break;
case INCLUDES:
case STARTSW:
case ENDSW:
op = " LIKE ";
break;
}
field = $f->getText($f);
/* Field lookup */
rqfp = rsp_query_field_lookup((char *)field->chars);
if (!rqfp)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
$valid = 0;
goto strcrit_valid_0; /* ABORT */
}
/* Check field type */
if (rqfp->field_type != RSP_TYPE_STRING)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a string field\n", field->chars);
$valid = 0;
goto strcrit_valid_0; /* ABORT */
}
escaped = db_escape_string((char *)$s->getText($s)->chars);
if (!escaped)
{
DPRINTF(E_LOG, L_RSP, "Could not escape value\n");
$valid = 0;
goto strcrit_valid_0; /* ABORT */
}
$result = field->factory->newRaw(field->factory);
$result->appendS($result, field);
$result->append8($result, op);
$result->append8($result, "'");
if ((optok == INCLUDES) || (optok == STARTSW))
$result->append8($result, "\%");
$result->append8($result, escaped);
if ((optok == INCLUDES) || (optok == ENDSW))
$result->append8($result, "\%");
$result->append8($result, "'");
strcrit_valid_0:
;
if (escaped)
free(escaped);
}
;
strop returns [ pANTLR3_COMMON_TOKEN op ]
: n = EQUAL
{ $op = $n->getToken($n); }
| n = INCLUDES
{ $op = $n->getToken($n); }
| n = STARTSW
{ $op = $n->getToken($n); }
| n = ENDSW
{ $op = $n->getToken($n); }
;
intcrit returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(o = intop f = FIELD i = INT)
{
char *op;
struct rsp_query_field_map *rqfp;
pANTLR3_STRING field;
op = NULL;
switch ($o.op->getType($o.op))
{
case EQUAL:
op = " = ";
break;
case LESS:
op = " < ";
break;
case GREATER:
op = " > ";
break;
case LTE:
op = " <= ";
break;
case GTE:
op = " >= ";
break;
}
field = $f->getText($f);
/* Field lookup */
rqfp = rsp_query_field_lookup((char *)field->chars);
if (!rqfp)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
$valid = 0;
goto intcrit_valid_0; /* ABORT */
}
/* Check field type */
if (rqfp->field_type != RSP_TYPE_INT)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not an integer field\n", field->chars);
$valid = 0;
goto intcrit_valid_0; /* ABORT */
}
$result = field->factory->newRaw(field->factory);
$result->appendS($result, field);
$result->append8($result, op);
$result->appendS($result, $i->getText($i));
intcrit_valid_0:
;
}
;
intop returns [ pANTLR3_COMMON_TOKEN op ]
: n = EQUAL
{ $op = $n->getToken($n); }
| n = LESS
{ $op = $n->getToken($n); }
| n = GREATER
{ $op = $n->getToken($n); }
| n = LTE
{ $op = $n->getToken($n); }
| n = GTE
{ $op = $n->getToken($n); }
;
datecrit returns [ pANTLR3_STRING result, int valid ]
@init { $result = NULL; $valid = 1; }
: ^(o = dateop f = FIELD d = datespec)
{
char *op;
struct rsp_query_field_map *rqfp;
pANTLR3_STRING field;
char buf[32];
int ret;
op = NULL;
switch ($o.op->getType($o.op))
{
case BEFORE:
op = " < ";
break;
case AFTER:
op = " > ";
break;
}
field = $f->getText($f);
/* Field lookup */
rqfp = rsp_query_field_lookup((char *)field->chars);
if (!rqfp)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a valid field in queries\n", field->chars);
$valid = 0;
goto datecrit_valid_0; /* ABORT */
}
/* Check field type */
if (rqfp->field_type != RSP_TYPE_DATE)
{
DPRINTF(E_LOG, L_RSP, "Field '\%s' is not a date field\n", field->chars);
$valid = 0;
goto datecrit_valid_0; /* ABORT */
}
ret = snprintf(buf, sizeof(buf), "\%ld", $d.date);
if ((ret < 0) || (ret >= sizeof(buf)))
{
DPRINTF(E_LOG, L_RSP, "Date \%ld too large for buffer, oops!\n", $d.date);
$valid = 0;
goto datecrit_valid_0; /* ABORT */
}
$result = field->factory->newRaw(field->factory);
$result->appendS($result, field);
$result->append8($result, op);
$result->append8($result, buf);
datecrit_valid_0:
;
}
;
dateop returns [ pANTLR3_COMMON_TOKEN op ]
: n = BEFORE
{ $op = $n->getToken($n); }
| n = AFTER
{ $op = $n->getToken($n); }
;
datespec returns [ time_t date, int valid ]
@init { $date = 0; $valid = 1; }
: r = dateref
{
if (!$r.valid)
$valid = 0;
else
$date = $r.date;
}
| ^(o = dateop r = dateref m = INT i = dateintval)
{
int val;
int ret;
if (!$r.valid || !$i.valid)
{
$valid = 0;
goto datespec_valid_0; /* ABORT */
}
ret = safe_atoi((char *)$m->getText($m)->chars, &val);
if (ret < 0)
{
DPRINTF(E_LOG, L_RSP, "Could not convert '\%s' to integer\n", $m->getText($m));
$valid = 0;
goto datespec_valid_0; /* ABORT */
}
switch ($o.op->getType($o.op))
{
case BEFORE:
$date = $r.date - (val * $i.period);
break;
case AFTER:
$date = $r.date + (val * $i.period);
break;
}
datespec_valid_0:
;
}
;
dateref returns [ time_t date, int valid ]
@init { $date = 0; $valid = 1; }
: n = DATE
{
struct tm tm;
char *ret;
ret = strptime((char *)$n->getText($n), "\%Y-\%m-\%d", &tm);
if (!ret)
{
DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be interpreted\n", $n->getText($n));
$valid = 0;
goto dateref_valid_0; /* ABORT */
}
else
{
if (*ret != '\0')
DPRINTF(E_LOG, L_RSP, "Garbage at end of date '\%s' ?!\n", $n->getText($n));
$date = mktime(&tm);
if ($date == (time_t) -1)
{
DPRINTF(E_LOG, L_RSP, "Date '\%s' could not be converted to an epoch\n", $n->getText($n));
$valid = 0;
goto dateref_valid_0; /* ABORT */
}
}
dateref_valid_0:
;
}
| TODAY
{ $date = time(NULL); }
;
dateintval returns [ time_t period, int valid ]
@init { $period = 0; $valid = 1; }
: DAY
{ $period = 24 * 60 * 60; }
| WEEK
{ $period = 7 * 24 * 60 * 60; }
| MONTH
{ $period = 30 * 24 * 60 * 60; }
| YEAR
{ $period = 365 * 24 * 60 * 60; }
;