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:
Julien BLACHE 2009-06-04 15:45:22 +02:00
parent 272d8bca04
commit f9d9964914
7 changed files with 931 additions and 2 deletions

1
src/.gitignore vendored
View File

@ -4,5 +4,6 @@ mt-daapd
*Lexer.[ch]
*Parser.[ch]
DAAP2SQL.[ch]
RSP2SQL.[ch]
*.u

View File

@ -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)

148
src/RSP.g Normal file
View File

@ -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';

453
src/RSP2SQL.g Normal file
View File

@ -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; }
;

View File

@ -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);
}

279
src/rsp_query.c Normal file
View File

@ -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);
}

31
src/rsp_query.h Normal file
View File

@ -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__ */