add query/filter support

This commit is contained in:
Ron Pedde 2005-03-14 06:17:28 +00:00
parent 352f627471
commit 8e6c0d2c6d
7 changed files with 285 additions and 371 deletions

View File

@ -45,7 +45,7 @@ CREATE TABLE config (
CREATE TABLE playlists ( CREATE TABLE playlists (
id INTEGER PRIMARY KEY NOT NULL, id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
smart INTEGER NOT NULL, smart INTEGER NOT NULL,
items INTEGER NOT NULL, items INTEGER NOT NULL,
query VARCHAR(1024) query VARCHAR(1024)

View File

@ -38,7 +38,7 @@ typedef enum {
// song meta data // song meta data
metaSongAlbum = firstTypeSpecificMetaId, metaSongAlbum = firstTypeSpecificMetaId,
metaSongArtist, metaSongArtist,
metaSongBPM, /* beats per minute */ metaSongBPM,
metaSongBitRate, metaSongBitRate,
metaSongComment, metaSongComment,
metaSongCompilation, metaSongCompilation,
@ -105,8 +105,7 @@ typedef struct tag_dbqueryinfo {
char *whereclause; char *whereclause;
} DBQUERYINFO; } DBQUERYINFO;
typedef struct typedef struct {
{
const char* tag; const char* tag;
MetaFieldName_t bit; MetaFieldName_t bit;
} METAMAP; } METAMAP;

View File

@ -488,6 +488,8 @@ int db_sqlite_enum_start(DBQUERYINFO *pinfo) {
if(pinfo->whereclause) { if(pinfo->whereclause) {
if(have_clause) if(have_clause)
strcat(query_rest," AND "); strcat(query_rest," AND ");
else
strcpy(query_rest," WHERE ");
strcat(query_rest,"("); strcat(query_rest,"(");
strcat(query_rest,pinfo->whereclause); strcat(query_rest,pinfo->whereclause);
@ -628,7 +630,6 @@ int db_sqlite_enum_fetch(DBQUERYINFO *pinfo, unsigned char **pdmap) {
if(!presult) if(!presult)
return 0; return 0;
db_sqlite_build_dmap(pinfo,(char**)valarray,presult,result_size); db_sqlite_build_dmap(pinfo,(char**)valarray,presult,result_size);
DPRINTF(E_DBG,L_DB,"Building response for %s (size %d)\n",valarray[3],result_size);
*pdmap = presult; *pdmap = presult;
return result_size; return result_size;
} }

View File

@ -40,6 +40,7 @@
#include "dynamic-art.h" #include "dynamic-art.h"
#include "restart.h" #include "restart.h"
#include "daapd.h" #include "daapd.h"
#include "query.h"
/* Forwards */ /* Forwards */
static void dispatch_server_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi); static void dispatch_server_info(WS_CONNINFO *pwsc, DBQUERYINFO *pqi);
@ -87,6 +88,7 @@ int daap_auth(char *username, char *password) {
void daap_handler(WS_CONNINFO *pwsc) { void daap_handler(WS_CONNINFO *pwsc) {
DBQUERYINFO *pqi; DBQUERYINFO *pqi;
char *token, *string, *save; char *token, *string, *save;
char *query;
pqi=(DBQUERYINFO*)malloc(sizeof(DBQUERYINFO)); pqi=(DBQUERYINFO*)malloc(sizeof(DBQUERYINFO));
if(!pqi) { if(!pqi) {
@ -96,6 +98,13 @@ void daap_handler(WS_CONNINFO *pwsc) {
memset(pqi,0x00,sizeof(DBQUERYINFO)); memset(pqi,0x00,sizeof(DBQUERYINFO));
query=ws_getvar(pwsc,"query");
if(!query) query=ws_getvar(pwsc,"filter");
if(query) {
DPRINTF(E_DBG,L_DAAP,"Getting sql clause for %s\n",query);
pqi->whereclause = query_build_sql(query);
}
/* Add some default headers */ /* Add some default headers */
ws_addresponseheader(pwsc,"Accept-Ranges","bytes"); ws_addresponseheader(pwsc,"Accept-Ranges","bytes");
ws_addresponseheader(pwsc,"DAAP-Server","mt-daapd/" VERSION); ws_addresponseheader(pwsc,"DAAP-Server","mt-daapd/" VERSION);

View File

@ -4,55 +4,109 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
#include "db-generic.h"
#include "err.h" #include "err.h"
#include "query.h" #include "query.h"
static const query_field_t* find_field(const char* name, static query_node_t* query_build(const char* query);
const query_field_t* fields); static void query_free(query_node_t* query);
static int arith_query(query_node_t* query, void* target); static int query_build_clause(query_node_t *query, char **current, int *size);
static int string_query(query_node_t* query, void* target);
static query_node_t* match_specifier(const char* query, static const query_field_t *find_field(const char* name,
const query_field_t* fields);
// static int arith_query(query_node_t* query, void* target);
// static int string_query(query_node_t* query, void* target);
static query_node_t *match_specifier(const char* query,
const char** cursor, const char** cursor,
const query_field_t* fields); const query_field_t* fields);
static query_node_t* group_match(const char* query,
static query_node_t *group_match(const char* query,
const char** cursor, const char** cursor,
const query_field_t* fields); const query_field_t* fields);
static query_node_t* single_match(const char* query,
static query_node_t *single_match(const char* query,
const char** cursor, const char** cursor,
const query_field_t* fields); const query_field_t* fields);
static int get_field_name(const char** pcursor, static int get_field_name(const char** pcursor,
const char* query, const char* query,
char* name, char* name,
int len); int len);
/*
static int get_opcode(const char** pcursor,
const char* query,
char* name,
int len);
*/
static query_node_t* match_number(const query_field_t* field,
char not, char opcode,
const char** pcursor,
const char* query);
static query_node_t* match_string(const query_field_t* field,
char not, char opcode,
const char** pcursor,
const char* query);
char* query_unescape(const char* query);
query_node_t* query_build(const char* query, const query_field_t* fields) static query_node_t *match_number(const query_field_t* field,
{ char not, char opcode,
const char** pcursor,
const char* query);
static query_node_t *match_string(const query_field_t* field,
char not, char opcode,
const char** pcursor,
const char* query);
char *query_unescape(const char* query);
static char *query_lookup_name(char *name);
static query_field_t song_fields[] = {
{ qft_string, "dmap.itemname", "title" },
{ qft_i32, "dmap.itemid", "id" },
{ qft_string, "daap.songalbum", "album" },
{ qft_string, "daap.songartist", "artist" },
{ qft_i32, "daap.songbitrate", "bitrate" },
{ qft_string, "daap.songcomment", "comment" },
{ qft_i32, "daap.songcompilation", "compilation" },
{ qft_string, "daap.songcomposer", "composer" },
{ qft_i32, "daap.songdatakind", "data_kind" },
{ qft_string, "daap.songdataurl", "url" },
{ qft_i32, "daap.songdateadded", "time_added" },
{ qft_i32, "daap.songdatemodified","time_modified" },
{ qft_string, "daap.songdescription", "description" },
{ qft_i32, "daap.songdisccount", "total_discs" },
{ qft_i32, "daap.songdiscnumber", "disc" },
{ qft_string, "daap.songformat", "type" },
{ qft_string, "daap.songgenre", "genre" },
{ qft_i32, "daap.songsamplerate", "samplerate" },
{ qft_i32, "daap.songsize", "file_size" },
// { qft_i32_const, "daap.songstarttime", 0 },
{ qft_i32, "daap.songstoptime", "song_length" },
{ qft_i32, "daap.songtime", "song_length" },
{ qft_i32, "daap.songtrackcount", "total_tracks" },
{ qft_i32, "daap.songtracknumber", "track" },
{ qft_i32, "daap.songyear", "year" },
{ 0, NULL, NULL }
};
char *query_build_sql(char *query) {
query_node_t *pquery;
char sql[2048];
char *sqlptr=sql;
int size=sizeof(sql);
pquery=query_build(query);
if(!query_build_clause(pquery,&sqlptr,&size)) {
query_free(pquery);
return strdup(sql);
}
query_free(pquery);
return NULL;
}
query_node_t* query_build(const char* query) {
query_node_t* left = 0; query_node_t* left = 0;
char* raw = query_unescape(query); char* raw = query_unescape(query);
const char* cursor = raw; const char* cursor = raw;
query_node_t* right = 0; query_node_t* right = 0;
query_type_t join; query_type_t join;
if(0 == (left = match_specifier(query, &cursor, fields))) if(0 == (left = match_specifier(query, &cursor, song_fields)))
goto error; goto error;
while(*cursor) while(*cursor)
@ -72,7 +126,7 @@ query_node_t* query_build(const char* query, const query_field_t* fields)
cursor++; cursor++;
if(0 == (right = match_specifier(raw, &cursor, fields))) if(0 == (right = match_specifier(raw, &cursor, song_fields)))
goto error; goto error;
con = (query_node_t*) calloc(1, sizeof(*con)); con = (query_node_t*) calloc(1, sizeof(*con));
@ -99,12 +153,12 @@ query_node_t* query_build(const char* query, const query_field_t* fields)
static query_node_t* match_specifier(const char* query, static query_node_t* match_specifier(const char* query,
const char** cursor, const char** cursor,
const query_field_t* fields) const query_field_t* fields) {
{ switch(**cursor) {
switch(**cursor) case '\'':
{ return single_match(query, cursor, fields);
case '\'': return single_match(query, cursor, fields); case '(':
case '(': return group_match(query, cursor, fields); return group_match(query, cursor, fields);
} }
DPRINTF(E_LOG,L_QRY,"Illegal character '%c' (0%o) at index %d: %s\n", DPRINTF(E_LOG,L_QRY,"Illegal character '%c' (0%o) at index %d: %s\n",
@ -246,17 +300,14 @@ static query_node_t* single_match(const char* query,
static int get_field_name(const char** pcursor, static int get_field_name(const char** pcursor,
const char* query, const char* query,
char* name, char* name,
int len) int len) {
{
const char* cursor = *pcursor; const char* cursor = *pcursor;
if(!isalpha(*cursor)) if(!isalpha(*cursor))
return 0; return 0;
while(isalpha(*cursor) || *cursor == '.') while(isalpha(*cursor) || *cursor == '.') {
{ if(--len <= 0) {
if(--len <= 0)
{
DPRINTF(E_LOG,L_QRY,"token length exceeded at offset %d: %s\n", DPRINTF(E_LOG,L_QRY,"token length exceeded at offset %d: %s\n",
cursor - query, query); cursor - query, query);
return 0; return 0;
@ -331,32 +382,26 @@ static query_node_t* match_string(const query_field_t* field,
query_type_t op = qot_is; query_type_t op = qot_is;
query_node_t* node; query_node_t* node;
if(opcode != ':') if(opcode != ':') {
{
DPRINTF(E_LOG,L_QRY,"Illegal operation on string: %c at index %d: %s\n", DPRINTF(E_LOG,L_QRY,"Illegal operation on string: %c at index %d: %s\n",
opcode, cursor - query - 1); opcode, cursor - query - 1);
return NULL; return NULL;
} }
if(*cursor == '*') if(*cursor == '*') {
{
op = qot_ends; op = qot_ends;
cursor++; cursor++;
} }
while(*cursor && *cursor != '\'') while(*cursor && *cursor != '\'') {
{ if(--left == 0) {
if(--left == 0)
{
DPRINTF(E_LOG,L_QRY,"string too long at index %d: %s\n", DPRINTF(E_LOG,L_QRY,"string too long at index %d: %s\n",
cursor - query, query); cursor - query, query);
return NULL; return NULL;
} }
if(*cursor == '\\') if(*cursor == '\\') {
{ switch(*++cursor) {
switch(*++cursor)
{
case '*': case '*':
case '\'': case '\'':
case '\\': case '\\':
@ -367,13 +412,12 @@ static query_node_t* match_string(const query_field_t* field,
*cursor, *cursor, cursor - query, query); *cursor, *cursor, cursor - query, query);
return NULL; return NULL;
} }
} } else {
else
*dst++ = *cursor++; *dst++ = *cursor++;
} }
}
if(dst[-1] == '*') if(dst[-1] == '*') {
{
op = (op == qot_is) ? qot_begins : qot_contains; op = (op == qot_is) ? qot_begins : qot_contains;
dst--; dst--;
} }
@ -390,70 +434,25 @@ static query_node_t* match_string(const query_field_t* field,
return node; return node;
} }
int query_test(query_node_t* query, void* target)
{
switch(query->type)
{
/* conjunction */
case qot_and:
return (query_test(query->left.node, target) &&
query_test(query->right.node, target));
case qot_or: void query_free(query_node_t* query) {
return (query_test(query->left.node, target) ||
query_test(query->right.node, target));
/* negation */
case qot_not:
return !query_test(query->left.node, target);
/* arithmetic */
case qot_eq:
case qot_ne:
case qot_le:
case qot_lt:
case qot_ge:
case qot_gt:
return arith_query(query, target);
/* string */
case qot_is:
case qot_begins:
case qot_ends:
case qot_contains:
return string_query(query, target);
break;
/* constants */
case qot_const:
return query->left.constant;
default:
return 0;
}
/* should not happen */
return 0;
}
void query_free(query_node_t* query)
{
if(0 != query) if(0 != query)
{ {
switch(query->type) switch(query->type)
{ {
/* conjunction */ // conjunction
case qot_and: case qot_and:
case qot_or: case qot_or:
query_free(query->left.node); query_free(query->left.node);
query_free(query->right.node); query_free(query->right.node);
break; break;
/* negation */ // negation
case qot_not: case qot_not:
query_free(query->left.node); query_free(query->left.node);
break; break;
/* arithmetic */ // arithmetic
case qot_eq: case qot_eq:
case qot_ne: case qot_ne:
case qot_le: case qot_le:
@ -462,7 +461,7 @@ void query_free(query_node_t* query)
case qot_gt: case qot_gt:
break; break;
/* string */ // string
case qot_is: case qot_is:
case qot_begins: case qot_begins:
case qot_ends: case qot_ends:
@ -470,7 +469,7 @@ void query_free(query_node_t* query)
free(query->right.str); free(query->right.str);
break; break;
/* constants */ // constants
case qot_const: case qot_const:
break; break;
@ -483,13 +482,11 @@ void query_free(query_node_t* query)
} }
} }
static const query_field_t* find_field(const char* name, const query_field_t* fields) static const query_field_t* find_field(const char* name, const query_field_t* fields) {
{
while(fields->name && strcasecmp(fields->name, name)) while(fields->name && strcasecmp(fields->name, name))
fields++; fields++;
if(fields->name == 0) if(fields->name == 0) {
{
DPRINTF(E_LOG,L_QRY,"Illegal query field: %s\n", name); DPRINTF(E_LOG,L_QRY,"Illegal query field: %s\n", name);
return NULL; return NULL;
} }
@ -497,186 +494,98 @@ static const query_field_t* find_field(const char* name, const query_field_t* fi
return fields; return fields;
} }
static int arith_query(query_node_t* query, void* target) int query_add_string(char **current, int *size, char *fmt, ...) {
{ va_list ap;
const query_field_t* field = query->left.field; int write_size;
switch(field->type) va_start(ap, fmt);
{ write_size=vsnprintf(*current, *size, fmt, ap);
case qft_i32: va_end(ap);
{
int tv = * (int*) ((size_t) target + field->offset);
tv -= query->right.i32;
switch(query->type)
{
case qot_eq: return tv == 0;
case qot_ne: return tv != 0;
case qot_le: return tv <= 0;
case qot_lt: return tv < 0;
case qot_ge: return tv >= 0;
case qot_gt: return tv > 0;
default:
DPRINTF(E_LOG,L_QRY,"illegal query type: %d\n", query->type);
break;
}
}
break;
case qft_i64:
{
long long tv = * (long long*) ((size_t) target + field->offset);
tv -= query->right.i32;
switch(query->type)
{
case qot_eq: return tv == 0;
case qot_ne: return tv != 0;
case qot_le: return tv <= 0;
case qot_lt: return tv < 0;
case qot_ge: return tv >= 0;
case qot_gt: return tv > 0;
default:
DPRINTF(E_LOG,L_QRY,"illegal query type: %d\n", query->type);
break;
}
}
break;
default:
DPRINTF(E_LOG,L_QRY,"illegal field type: %d\n", field->type);
break;
}
if(write_size > *size) {
*size=0;
return 0; return 0;
}
*size = *size - write_size;
return write_size;
} }
static int string_query(query_node_t* query, void* target) int query_build_clause(query_node_t *query, char **current, int *size) {
{ char* labels[] = {
const query_field_t* field = query->left.field;
const char* ts;
if(field->type != qft_string)
{
DPRINTF(E_LOG,L_QRY,"illegal field type: %d\n", field->type);
return 0;
}
ts = * (const char**) ((size_t) target + field->offset);
if(0 == ts)
return strlen(query->right.str) == 0;
switch(query->type)
{
case qot_is:
return !strcasecmp(query->right.str, ts);
case qot_begins:
return !strncasecmp(query->right.str, ts, strlen(query->right.str));
case qot_ends:
{
int start = strlen(ts) - strlen(query->right.str);
if(start < 0)
return 0;
return !strcasecmp(query->right.str, ts + start);
}
case qot_contains:
return (int) strcasestr(ts, query->right.str); /* returns null if not found */
default:
DPRINTF(E_LOG,L_QRY,"Illegal query type: %d\n", query->type);
break;
}
return 0;
}
void query_dump(FILE* fp, query_node_t* query, int depth)
{
static const char* labels[] = {
"NOP", "NOP",
"and", "AND",
"or", "OR",
"not", "NOT",
"==", "=",
"!=", "<>",
"<=", "<=",
"<", "<",
">=", ">=",
">", ">",
"eq", "=",
"beginswith", " (%s LIKE '%s\%) ",
"endwith", " (%s LIKE '\%%s') ",
"contains", " (%s LIKE '\%%s\%') ",
"constant" "constant"
}; };
#ifndef DEBUG switch(query->type) {
return;
#endif
switch(query->type)
{
case qot_and: case qot_and:
case qot_or: case qot_or:
fprintf(fp, "%*s(%s\n", depth, "", labels[query->type]); if(*size) (*current) += query_add_string(current,size," (");
query_dump(fp, query->left.node, depth + 4); if(query_build_clause(query->left.node,current,size)) return 1;
query_dump(fp, query->right.node, depth + 4); if(*size) (*current) += query_add_string(current,size," %s ", labels[query->type]);
fprintf(fp, "%*s)\n", depth, ""); if(query_build_clause(query->right.node,current,size)) return 1;
if(*size) (*current) += query_add_string(current,size,") ");
break; break;
case qot_not: case qot_not:
fprintf(fp, "%*s(not\n", depth, ""); if(*size) (*current) += query_add_string(current,size," (NOT ");
query_dump(fp, query->left.node, depth + 4); if(query_build_clause(query->left.node,current,size)) return 1;
fprintf(fp, "%*s)\n", depth, ""); if(*size) (*current) += query_add_string(current,size,") ");
break; break;
/* arithmetic */
case qot_eq: case qot_eq:
case qot_ne: case qot_ne:
case qot_le: case qot_le:
case qot_lt: case qot_lt:
case qot_ge: case qot_ge:
case qot_gt: case qot_gt:
if(query->left.field->type == qft_i32) if(*size) (*current) += query_add_string(current,size," (%s %s ",
fprintf(fp, "%*s(%s %s %d)\n", query->left.field->fieldname,
depth, "", labels[query->type], labels[query->type]);
query->left.field->name, query->right.i32); if(query->left.field->type == qft_i32) {
else if(*size) (*current) += query_add_string(current,size," %d) ",query->right.i32);
fprintf(fp, "%*s(%s %s %ll)\n", } else {
depth, "", labels[query->type], if(*size) (*current) += query_add_string(current,size," %ll) ",query->right.i64);
query->left.field->name, query->right.i64); }
break; break;
/* string */
case qot_is: case qot_is:
if(*size)(*current) += query_add_string(current,size," (%s='%s') ",
query->left.field->fieldname,
query->right.str);
break;
case qot_begins: case qot_begins:
case qot_ends: case qot_ends:
case qot_contains: case qot_contains:
fprintf(fp, "%*s(%s %s \"%s\")\n", if(*size)(*current) += query_add_string(current,size,labels[query->type],
depth, "", labels[query->type], query->left.field->fieldname,
query->left.field->name, query->right.str); query->right.str);
break; break;
case qot_const: /* Not sure what this would be for */
/* constants */
case qot_const:
fprintf(fp, "%*s(%s)\n", depth, "", query->left.constant ? "true" : "false");
break; break;
default: default:
break; break;
} }
return 0;
} }
char* query_unescape(const char* src) char* query_unescape(const char* src) {
{
char* copy = malloc(strlen(src) + 1); char* copy = malloc(strlen(src) + 1);
char* dst = copy; char* dst = copy;

View File

@ -42,7 +42,7 @@ struct query_field_
{ {
query_type_t type; query_type_t type;
const char* name; const char* name;
int offset; const char* fieldname;
}; };
typedef struct query_node_ query_node_t; typedef struct query_node_ query_node_t;
@ -62,10 +62,6 @@ struct query_node_
} right; } right;
}; };
query_node_t* query_build(const char* query, extern char *query_build_sql(char *query);
const query_field_t* fields);
int query_test(query_node_t* query, void* target);
void query_free(query_node_t* query);
void query_dump(FILE* fp, query_node_t* query, int depth);
#endif #endif