Add new ANTLR parser for RSP queries
This parser actually supports way more than is needed for RSP proper, as mt-daapd was piggybacking on the RSP queries for its smart playlists. As I don't have (yet?) the RSP specs, better safe than sorry here. This will be revisited at some point. Or not.
This commit is contained in:
parent
272d8bca04
commit
f9d9964914
|
@ -4,5 +4,6 @@ mt-daapd
|
|||
*Lexer.[ch]
|
||||
*Parser.[ch]
|
||||
DAAP2SQL.[ch]
|
||||
RSP2SQL.[ch]
|
||||
|
||||
*.u
|
||||
|
|
|
@ -10,9 +10,12 @@ MUSEPACKSRC=scan-mpc.c
|
|||
endif
|
||||
|
||||
ANTLR_GRAMMARS = \
|
||||
RSP.g RSP2SQL.g \
|
||||
DAAP.g DAAP2SQL.g
|
||||
|
||||
ANTLR_SOURCES = \
|
||||
RSPLexer.c RSPLexer.h RSPParser.c RSPParser.h \
|
||||
RSP2SQL.c RSP2SQL.h \
|
||||
DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \
|
||||
DAAP2SQL.c DAAP2SQL.h
|
||||
|
||||
|
@ -33,6 +36,7 @@ mt_daapd_SOURCES = main.c \
|
|||
httpd_daap.c httpd_daap.h \
|
||||
transcode.c transcode.h \
|
||||
misc.c misc.h \
|
||||
rsp_query.c rsp_query.h \
|
||||
daap_query.c daap_query.h \
|
||||
db-generic.c db-generic.h \
|
||||
scan-wma.c \
|
||||
|
@ -52,6 +56,7 @@ EXTRA_DIST = \
|
|||
|
||||
|
||||
# Let's help the dependencies a little.
|
||||
rsp_query.c: RSPLexer.h RSPParser.h RSP2SQL.h
|
||||
daap_query.c: DAAPLexer.h DAAPParser.h DAAP2SQL.h
|
||||
|
||||
|
||||
|
@ -65,7 +70,7 @@ SUFFIXES = .g .u
|
|||
@echo -n "ANTLR_PRODUCTS += " > $@.tmp
|
||||
@grep : $@ | cut -d : -f 1 | tr -d ' ' | { while read f; do test "$$f" != "$<" && echo -n "$$f "; done } >> $@.tmp
|
||||
@cat $@.tmp >> $@
|
||||
rm $@.tmp
|
||||
@rm $@.tmp
|
||||
|
||||
clean-local:
|
||||
-rm $(ANTLR_PRODUCTS)
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
grammar RSP;
|
||||
|
||||
options {
|
||||
output = AST;
|
||||
ASTLabelType = pANTLR3_BASE_TREE;
|
||||
language = C;
|
||||
}
|
||||
|
||||
query : expr NEWLINE? EOF -> expr
|
||||
;
|
||||
|
||||
expr : aexpr (OR^ aexpr)*
|
||||
;
|
||||
|
||||
aexpr : crit (AND^ crit)*
|
||||
;
|
||||
|
||||
crit : LPAR expr RPAR -> expr
|
||||
| strcrit
|
||||
| intcrit
|
||||
| datecrit
|
||||
;
|
||||
|
||||
strcrit : FIELD strop STR -> ^(strop FIELD STR)
|
||||
| FIELD NOT strop STR -> ^(NOT ^(strop FIELD STR))
|
||||
;
|
||||
|
||||
strop : EQUAL
|
||||
| INCLUDES
|
||||
| STARTSW
|
||||
| ENDSW
|
||||
;
|
||||
|
||||
intcrit : FIELD intop INT -> ^(intop FIELD INT)
|
||||
| FIELD NOT intop INT -> ^(NOT ^(intop FIELD INT))
|
||||
;
|
||||
|
||||
intop : EQUAL
|
||||
| LESS
|
||||
| GREATER
|
||||
| LTE
|
||||
| GTE
|
||||
;
|
||||
|
||||
datecrit: FIELD dateop datespec -> ^(dateop FIELD datespec)
|
||||
;
|
||||
|
||||
dateop : BEFORE
|
||||
| AFTER
|
||||
;
|
||||
|
||||
datespec: dateref
|
||||
| INT dateintval dateop dateref -> ^(dateop dateref INT dateintval)
|
||||
;
|
||||
|
||||
dateref : DATE
|
||||
| TODAY
|
||||
;
|
||||
|
||||
dateintval
|
||||
: DAY
|
||||
| WEEK
|
||||
| MONTH
|
||||
| YEAR
|
||||
;
|
||||
|
||||
QUOTE : '"';
|
||||
LPAR : '(';
|
||||
RPAR : ')';
|
||||
|
||||
AND : 'and';
|
||||
OR : 'or';
|
||||
NOT : '!';
|
||||
|
||||
/* Both string & int */
|
||||
EQUAL : '=';
|
||||
|
||||
/* String */
|
||||
INCLUDES: 'includes';
|
||||
STARTSW : 'startswith';
|
||||
ENDSW : 'endswith';
|
||||
|
||||
/* Int */
|
||||
GREATER : '>';
|
||||
LESS : '<';
|
||||
GTE : '>=';
|
||||
LTE : '<=';
|
||||
|
||||
/* Date */
|
||||
BEFORE : 'before';
|
||||
AFTER : 'after';
|
||||
DAY : 'day' | 'days';
|
||||
WEEK : 'week' | 'weeks';
|
||||
MONTH : 'month' | 'months';
|
||||
YEAR : 'year' | 'years';
|
||||
TODAY : 'today';
|
||||
|
||||
NEWLINE : '\r'? '\n';
|
||||
|
||||
WS : (' ' | '\t') { $channel = HIDDEN; };
|
||||
|
||||
FIELD : 'a'..'z' ('a'..'z' | '_')* 'a'..'z';
|
||||
|
||||
INT : DIGIT19 DIGIT09*;
|
||||
|
||||
/* YYYY-MM-DD */
|
||||
DATE : DIGIT19 DIGIT09 DIGIT09 DIGIT09 '-' ('0' DIGIT19 | '1' '0'..'2') '-' ('0' DIGIT19 | '1'..'2' DIGIT09 | '3' '0'..'1');
|
||||
|
||||
/*
|
||||
Unescaping adapted from (ported to the C runtime)
|
||||
<http://stackoverflow.com/questions/504402/how-to-handle-escape-sequences-in-string-literals-in-antlr-3>
|
||||
*/
|
||||
STR
|
||||
@init{ pANTLR3_STRING unesc = GETTEXT()->factory->newRaw(GETTEXT()->factory); }
|
||||
: QUOTE ( reg = ~('\\' | '"') { unesc->addc(unesc, reg); }
|
||||
| esc = ESCAPED { unesc->appendS(unesc, GETTEXT()); } )+ QUOTE { SETTEXT(unesc); }
|
||||
;
|
||||
|
||||
fragment
|
||||
ESCAPED : '\\'
|
||||
( '\\' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\\")); }
|
||||
| '"' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\"")); }
|
||||
)
|
||||
;
|
||||
|
||||
fragment
|
||||
DIGIT09 : '0'..'9';
|
||||
|
||||
fragment
|
||||
DIGIT19 : '1'..'9';
|
|
@ -0,0 +1,453 @@
|
|||
/*
|
||||
* 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 "ff-dbstruct.h"
|
||||
#include "db-generic.h"
|
||||
#include "db-sql.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_sql_escape_dup("\%q", $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; }
|
||||
;
|
|
@ -45,6 +45,7 @@
|
|||
#include "httpd.h"
|
||||
#include "transcode.h"
|
||||
#include "httpd_rsp.h"
|
||||
#include "rsp_query.h"
|
||||
|
||||
|
||||
#define RSP_VERSION "1.0"
|
||||
|
@ -1049,6 +1050,10 @@ rsp_init(void)
|
|||
int i;
|
||||
int ret;
|
||||
|
||||
ret = rsp_query_init();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; rsp_handlers[i].handler; i++)
|
||||
{
|
||||
ret = regcomp(&rsp_handlers[i].preg, rsp_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
|
||||
|
@ -1057,11 +1062,16 @@ rsp_init(void)
|
|||
regerror(ret, &rsp_handlers[i].preg, buf, sizeof(buf));
|
||||
|
||||
DPRINTF(E_FATAL, L_RSP, "RSP init failed; regexp error: %s\n", buf);
|
||||
return -1;
|
||||
goto regexp_fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
regexp_fail:
|
||||
rsp_query_deinit();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1069,6 +1079,8 @@ rsp_deinit(void)
|
|||
{
|
||||
int i;
|
||||
|
||||
rsp_query_deinit();
|
||||
|
||||
for (i = 0; rsp_handlers[i].handler; i++)
|
||||
regfree(&rsp_handlers[i].preg);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <avl.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "misc.h"
|
||||
#include "rsp_query.h"
|
||||
|
||||
#include "RSPLexer.h"
|
||||
#include "RSPParser.h"
|
||||
#include "RSP2SQL.h"
|
||||
|
||||
|
||||
static struct rsp_query_field_map rsp_query_fields[] =
|
||||
{
|
||||
{ 0, RSP_TYPE_INT, "id" },
|
||||
{ 0, RSP_TYPE_STRING, "path" },
|
||||
{ 0, RSP_TYPE_STRING, "fname" },
|
||||
{ 0, RSP_TYPE_STRING, "title" },
|
||||
{ 0, RSP_TYPE_STRING, "artist" },
|
||||
{ 0, RSP_TYPE_STRING, "album" },
|
||||
{ 0, RSP_TYPE_STRING, "genre" },
|
||||
{ 0, RSP_TYPE_STRING, "comment" },
|
||||
{ 0, RSP_TYPE_STRING, "type" },
|
||||
{ 0, RSP_TYPE_STRING, "composer" },
|
||||
{ 0, RSP_TYPE_STRING, "orchestra" },
|
||||
{ 0, RSP_TYPE_STRING, "grouping" },
|
||||
{ 0, RSP_TYPE_STRING, "url" },
|
||||
{ 0, RSP_TYPE_INT, "bitrate" },
|
||||
{ 0, RSP_TYPE_INT, "samplerate" },
|
||||
{ 0, RSP_TYPE_INT, "song_length" },
|
||||
{ 0, RSP_TYPE_INT, "file_size" },
|
||||
{ 0, RSP_TYPE_INT, "year" },
|
||||
{ 0, RSP_TYPE_INT, "track" },
|
||||
{ 0, RSP_TYPE_INT, "total_tracks" },
|
||||
{ 0, RSP_TYPE_INT, "disc" },
|
||||
{ 0, RSP_TYPE_INT, "total_discs" },
|
||||
{ 0, RSP_TYPE_INT, "bpm" },
|
||||
{ 0, RSP_TYPE_INT, "compilation" },
|
||||
{ 0, RSP_TYPE_INT, "rating" },
|
||||
{ 0, RSP_TYPE_INT, "play_count" },
|
||||
{ 0, RSP_TYPE_INT, "data_kind" },
|
||||
{ 0, RSP_TYPE_INT, "item_kind" },
|
||||
{ 0, RSP_TYPE_STRING, "description" },
|
||||
{ 0, RSP_TYPE_DATE, "time_added" },
|
||||
{ 0, RSP_TYPE_DATE, "time_modified" },
|
||||
{ 0, RSP_TYPE_DATE, "time_played" },
|
||||
{ 0, RSP_TYPE_DATE, "db_timestamp" },
|
||||
{ 0, RSP_TYPE_INT, "sample_count" },
|
||||
{ 0, RSP_TYPE_INT, "force_update" },
|
||||
{ 0, RSP_TYPE_STRING, "codectype" },
|
||||
{ 0, RSP_TYPE_INT, "idx" },
|
||||
{ 0, RSP_TYPE_INT, "has_video" },
|
||||
{ 0, RSP_TYPE_INT, "contentrating" },
|
||||
{ 0, RSP_TYPE_INT, "bits_per_sample" },
|
||||
{ 0, RSP_TYPE_STRING, "album_artist" },
|
||||
|
||||
{ -1, -1, NULL }
|
||||
};
|
||||
|
||||
static avl_tree_t *rsp_query_fields_hash;
|
||||
|
||||
|
||||
static int
|
||||
rsp_query_field_map_compare(const void *aa, const void *bb)
|
||||
{
|
||||
struct rsp_query_field_map *a = (struct rsp_query_field_map *)aa;
|
||||
struct rsp_query_field_map *b = (struct rsp_query_field_map *)bb;
|
||||
|
||||
if (a->hash < b->hash)
|
||||
return -1;
|
||||
|
||||
if (a->hash > b->hash)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct rsp_query_field_map *
|
||||
rsp_query_field_lookup(char *field)
|
||||
{
|
||||
struct rsp_query_field_map rqfm;
|
||||
avl_node_t *node;
|
||||
|
||||
rqfm.hash = djb_hash(field, strlen(field));
|
||||
|
||||
node = avl_search(rsp_query_fields_hash, &rqfm);
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
return (struct rsp_query_field_map *)node->item;
|
||||
}
|
||||
|
||||
char *
|
||||
rsp_query_parse_sql(const char *rsp_query)
|
||||
{
|
||||
/* Input RSP query, fed to the lexer */
|
||||
pANTLR3_INPUT_STREAM query;
|
||||
|
||||
/* Lexer and the resulting token stream, fed to the parser */
|
||||
pRSPLexer lxr;
|
||||
pANTLR3_COMMON_TOKEN_STREAM tkstream;
|
||||
|
||||
/* Parser and the resulting AST, fed to the tree parser */
|
||||
pRSPParser psr;
|
||||
RSPParser_query_return qtree;
|
||||
pANTLR3_COMMON_TREE_NODE_STREAM nodes;
|
||||
|
||||
/* Tree parser and the resulting SQL query string */
|
||||
pRSP2SQL sqlconv;
|
||||
pANTLR3_STRING sql;
|
||||
|
||||
char *ret = NULL;
|
||||
|
||||
DPRINTF(E_DBG, L_RSP, "Trying RSP query -%s-\n", rsp_query);
|
||||
|
||||
query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)rsp_query, (ANTLR3_UINT64)strlen(rsp_query), (pANTLR3_UINT8)"RSP query");
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create input stream\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lxr = RSPLexerNew(query);
|
||||
if (!lxr)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create RSP lexer\n");
|
||||
goto lxr_fail;
|
||||
}
|
||||
|
||||
tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));
|
||||
if (!tkstream)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create RSP token stream\n");
|
||||
goto tkstream_fail;
|
||||
}
|
||||
|
||||
psr = RSPParserNew(tkstream);
|
||||
if (!psr)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create RSP parser\n");
|
||||
goto psr_fail;
|
||||
}
|
||||
|
||||
qtree = psr->query(psr);
|
||||
|
||||
/* Check for parser errors */
|
||||
if (psr->pParser->rec->state->errorCount > 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "RSP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount);
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM, L_RSP, "RSP query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars);
|
||||
|
||||
nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT);
|
||||
if (!nodes)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create node stream\n");
|
||||
goto psr_error;
|
||||
}
|
||||
|
||||
sqlconv = RSP2SQLNew(nodes);
|
||||
if (!sqlconv)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "Could not create SQL converter\n");
|
||||
goto sql_fail;
|
||||
}
|
||||
|
||||
sql = sqlconv->query(sqlconv);
|
||||
|
||||
/* Check for tree parser errors */
|
||||
if (sqlconv->pTreeParser->rec->state->errorCount > 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "RSP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount);
|
||||
goto sql_error;
|
||||
}
|
||||
|
||||
if (sql)
|
||||
{
|
||||
DPRINTF(E_DBG, L_RSP, "RSP SQL query: -%s-\n", sql->chars);
|
||||
ret = strdup((char *)sql->chars);
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINTF(E_LOG, L_RSP, "Invalid RSP query\n");
|
||||
ret = NULL;
|
||||
}
|
||||
|
||||
sql_error:
|
||||
sqlconv->free(sqlconv);
|
||||
sql_fail:
|
||||
nodes->free(nodes);
|
||||
psr_error:
|
||||
psr->free(psr);
|
||||
psr_fail:
|
||||
tkstream->free(tkstream);
|
||||
tkstream_fail:
|
||||
lxr->free(lxr);
|
||||
lxr_fail:
|
||||
query->close(query);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
rsp_query_init(void)
|
||||
{
|
||||
avl_node_t *node;
|
||||
struct rsp_query_field_map *rqfm;
|
||||
int i;
|
||||
|
||||
rsp_query_fields_hash = avl_alloc_tree(rsp_query_field_map_compare, NULL);
|
||||
if (!rsp_query_fields_hash)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_RSP, "RSP query init could not allocate AVL tree\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; rsp_query_fields[i].hash == 0; i++)
|
||||
{
|
||||
rsp_query_fields[i].hash = djb_hash(rsp_query_fields[i].rsp_field, strlen(rsp_query_fields[i].rsp_field));
|
||||
|
||||
node = avl_insert(rsp_query_fields_hash, &rsp_query_fields[i]);
|
||||
if (!node)
|
||||
{
|
||||
if (errno != EEXIST)
|
||||
DPRINTF(E_FATAL, L_RSP, "RSP query init failed; AVL insert error: %s\n", strerror(errno));
|
||||
else
|
||||
{
|
||||
node = avl_search(rsp_query_fields_hash, &rsp_query_fields[i]);
|
||||
rqfm = node->item;
|
||||
|
||||
DPRINTF(E_FATAL, L_RSP, "RSP query init failed; WARNING: duplicate hash key\n");
|
||||
DPRINTF(E_FATAL, L_RSP, "Hash %x, string %s\n", rsp_query_fields[i].hash, rsp_query_fields[i].rsp_field);
|
||||
|
||||
DPRINTF(E_FATAL, L_RSP, "Hash %x, string %s\n", rqfm->hash, rqfm->rsp_field);
|
||||
}
|
||||
|
||||
goto avl_insert_fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
avl_insert_fail:
|
||||
avl_free_tree(rsp_query_fields_hash);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
rsp_query_deinit(void)
|
||||
{
|
||||
avl_free_tree(rsp_query_fields_hash);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
#ifndef __RSP_QUERY_H__
|
||||
#define __RSP_QUERY_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define RSP_TYPE_STRING 0
|
||||
#define RSP_TYPE_INT 1
|
||||
#define RSP_TYPE_DATE 2
|
||||
|
||||
struct rsp_query_field_map {
|
||||
uint32_t hash;
|
||||
int field_type;
|
||||
char *rsp_field;
|
||||
/* RSP fields are named after the DB columns - or vice versa */
|
||||
};
|
||||
|
||||
|
||||
struct rsp_query_field_map *
|
||||
rsp_query_field_lookup(char *field);
|
||||
|
||||
char *
|
||||
rsp_query_parse_sql(const char *rsp_query);
|
||||
|
||||
int
|
||||
rsp_query_init(void);
|
||||
|
||||
void
|
||||
rsp_query_deinit(void);
|
||||
|
||||
#endif /* !__RSP_QUERY_H__ */
|
Loading…
Reference in New Issue