RetroArch/libretro-db/query.c

1002 lines
24 KiB
C

/* Copyright (C) 2010-2017 The RetroArch team
*
* ---------------------------------------------------------------------------------------
* The following license statement only applies to this file (query.c).
* ---------------------------------------------------------------------------------------
*
* Permission is hereby granted, free of charge,
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef _WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <compat/fnmatch.h>
#include <compat/strl.h>
#include <string/stdstring.h>
#include <retro_miscellaneous.h>
#include "libretrodb.h"
#include "query.h"
#include "rmsgpack_dom.h"
#define MAX_ERROR_LEN 256
#define QUERY_MAX_ARGS 50
struct buffer
{
const char *data;
size_t len;
ssize_t offset;
};
enum argument_type
{
AT_FUNCTION,
AT_VALUE
};
struct argument;
typedef struct rmsgpack_dom_value (*rarch_query_func)(
struct rmsgpack_dom_value input,
unsigned argc,
const struct argument *argv
);
struct invocation
{
struct argument *argv;
rarch_query_func func;
unsigned argc;
};
struct argument
{
union
{
struct rmsgpack_dom_value value;
struct invocation invocation;
} a;
enum argument_type type;
};
struct query
{
struct invocation root; /* ptr alignment */
unsigned ref_count;
};
struct registered_func
{
const char *name;
rarch_query_func func;
};
/* Forward declarations */
static struct buffer query_parse_method_call(char *s, size_t len,
struct buffer buff,
struct invocation *invocation, const char **error);
static struct buffer query_parse_table(char *s,
size_t len, struct buffer buff,
struct invocation *invocation, const char **error);
/* Errors */
static struct rmsgpack_dom_value query_func_is_true(
struct rmsgpack_dom_value input,
unsigned argc, const struct argument *argv)
{
struct rmsgpack_dom_value res;
res.type = RDT_BOOL;
res.val.bool_ = 0;
if (!(argc > 0 || input.type != RDT_BOOL))
res.val.bool_ = input.val.bool_;
return res;
}
static struct rmsgpack_dom_value func_equals(
struct rmsgpack_dom_value input,
unsigned argc, const struct argument * argv)
{
struct argument arg;
struct rmsgpack_dom_value res;
res.type = RDT_BOOL;
res.val.bool_ = 0;
if (argc == 1)
{
arg = argv[0];
if (arg.type == AT_VALUE)
{
if ( input.type == RDT_UINT &&
arg.a.value.type == RDT_INT)
{
arg.a.value.type = RDT_UINT;
arg.a.value.val.uint_ = arg.a.value.val.int_;
}
res.val.bool_ = (rmsgpack_dom_value_cmp(&input, &arg.a.value) == 0);
}
}
return res;
}
static struct rmsgpack_dom_value query_func_operator_or(
struct rmsgpack_dom_value input,
unsigned argc, const struct argument * argv)
{
unsigned i;
struct rmsgpack_dom_value res;
res.type = RDT_BOOL;
res.val.bool_ = 0;
for (i = 0; i < argc; i++)
{
if (argv[i].type == AT_VALUE)
res = func_equals(input, 1, &argv[i]);
else
res = query_func_is_true(
argv[i].a.invocation.func(input,
argv[i].a.invocation.argc,
argv[i].a.invocation.argv
), 0, NULL);
if (res.val.bool_)
break;
}
return res;
}
static struct rmsgpack_dom_value query_func_operator_and(
struct rmsgpack_dom_value input,
unsigned argc, const struct argument * argv)
{
unsigned i;
struct rmsgpack_dom_value res;
res.type = RDT_BOOL;
res.val.bool_ = 0;
for (i = 0; i < argc; i++)
{
if (argv[i].type == AT_VALUE)
res = func_equals(input, 1, &argv[i]);
else
res = query_func_is_true(
argv[i].a.invocation.func(input,
argv[i].a.invocation.argc,
argv[i].a.invocation.argv
),
0, NULL);
if (!res.val.bool_)
break;
}
return res;
}
static struct rmsgpack_dom_value query_func_between(
struct rmsgpack_dom_value input,
unsigned argc, const struct argument * argv)
{
struct rmsgpack_dom_value res;
res.type = RDT_BOOL;
res.val.bool_ = 0;
if (argc != 2)
return res;
if ( argv[0].type != AT_VALUE
|| argv[1].type != AT_VALUE)
return res;
if ( argv[0].a.value.type != RDT_INT
|| argv[1].a.value.type != RDT_INT)
return res;
switch (input.type)
{
case RDT_INT:
res.val.bool_ = (
(input.val.int_ >= argv[0].a.value.val.int_)
&& (input.val.int_ <= argv[1].a.value.val.int_));
break;
case RDT_UINT:
res.val.bool_ = (
((unsigned)input.val.int_ >= argv[0].a.value.val.uint_)
&& (input.val.int_ <= argv[1].a.value.val.int_));
break;
default:
break;
}
return res;
}
static struct rmsgpack_dom_value query_func_glob(
struct rmsgpack_dom_value input,
unsigned argc, const struct argument * argv)
{
struct rmsgpack_dom_value res;
unsigned i = 0;
res.type = RDT_BOOL;
res.val.bool_ = 0;
(void)i;
if (argc != 1)
return res;
if (argv[0].type != AT_VALUE || argv[0].a.value.type != RDT_STRING)
return res;
if (input.type != RDT_STRING)
return res;
res.val.bool_ = rl_fnmatch(
argv[0].a.value.val.string.buff,
input.val.string.buff,
0
) == 0;
return res;
}
struct registered_func registered_functions[100] = {
{"is_true", query_func_is_true},
{"or", query_func_operator_or},
{"and", query_func_operator_and},
{"between", query_func_between},
{"glob", query_func_glob},
{NULL, NULL}
};
static void query_raise_unknown_function(
char *s, size_t _len,
ssize_t where, const char *name,
ssize_t len, const char **error)
{
int n = snprintf(s, _len,
"%" PRIu64 "::Unknown function '",
(uint64_t)where
);
if (len < (_len - n - 3))
strncpy(s + n, name, len);
strcpy_literal(s + n + len, "'");
*error = s;
}
static void query_argument_free(struct argument *arg)
{
unsigned i;
if (arg->type != AT_FUNCTION)
{
rmsgpack_dom_value_free(&arg->a.value);
return;
}
for (i = 0; i < arg->a.invocation.argc; i++)
query_argument_free(&arg->a.invocation.argv[i]);
free((void*)arg->a.invocation.argv);
}
static struct buffer query_parse_integer(
char *s, size_t len,
struct buffer buff,
struct rmsgpack_dom_value *value,
const char **error)
{
bool test = false;
value->type = RDT_INT;
test = (sscanf(buff.data + buff.offset,
STRING_REP_INT64,
(int64_t*)&value->val.int_) == 0);
if (test)
{
snprintf(s, len,
"%" PRIu64 "::Expected number",
(uint64_t)buff.offset);
*error = s;
}
else
{
while (ISDIGIT((int)buff.data[buff.offset]))
buff.offset++;
}
return buff;
}
static struct buffer query_chomp(struct buffer buff)
{
for (; (unsigned)buff.offset < buff.len
&& ISSPACE((int)buff.data[buff.offset]); buff.offset++);
return buff;
}
static struct buffer query_expect_eof(char *s, size_t len,
struct buffer buff, const char ** error)
{
buff = query_chomp(buff);
if ((unsigned)buff.offset < buff.len)
{
snprintf(s, len,
"%" PRIu64 "::Expected EOF found '%c'",
(uint64_t)buff.offset,
buff.data[buff.offset]
);
*error = s;
}
return buff;
}
static int query_peek(struct buffer buff, const char * data,
size_t size_data)
{
size_t remain = buff.len - buff.offset;
if (remain < size_data)
return 0;
return (strncmp(buff.data + buff.offset,
data, size_data) == 0);
}
static int query_is_eot(struct buffer buff)
{
return ((unsigned)buff.offset >= buff.len);
}
static struct buffer query_get_char(
char *s, size_t len,
struct buffer buff, char * c,
const char ** error)
{
if (query_is_eot(buff))
{
snprintf(s, len,
"%" PRIu64 "::Unexpected EOF",
(uint64_t)buff.offset
);
*error = s;
return buff;
}
*c = buff.data[buff.offset];
buff.offset++;
return buff;
}
static struct buffer query_parse_string(
char *s, size_t len,
struct buffer buff,
struct rmsgpack_dom_value *value, const char **error)
{
const char * str_start = NULL;
char terminator = '\0';
char c = '\0';
int is_binstr = 0;
buff = query_get_char(s, len,buff, &terminator, error);
if (*error)
return buff;
if (terminator == 'b')
{
is_binstr = 1;
buff = query_get_char(s, len,
buff, &terminator, error);
}
if (terminator != '"' && terminator != '\'')
{
buff.offset--;
snprintf(s, len,
"%" PRIu64 "::Expected string",
(uint64_t)buff.offset);
*error = s;
}
str_start = buff.data + buff.offset;
buff = query_get_char(s, len, buff, &c, error);
while (!*error)
{
if (c == terminator)
break;
buff = query_get_char(s, len, buff, &c, error);
}
if (!*error)
{
size_t count;
value->type = is_binstr ? RDT_BINARY : RDT_STRING;
value->val.string.len = (uint32_t)((buff.data + buff.offset) - str_start - 1);
count = is_binstr ? (value->val.string.len + 1) / 2
: (value->val.string.len + 1);
value->val.string.buff = (char*)calloc(count, sizeof(char));
if (!value->val.string.buff)
{
strcpy_literal(s, "Out of memory");
*error = s;
}
else if (is_binstr)
{
unsigned i;
const char *tok = str_start;
unsigned j = 0;
for (i = 0; i < value->val.string.len; i += 2)
{
uint8_t hi, lo;
char hic = tok[i];
char loc = tok[i + 1];
if (hic <= '9')
hi = hic - '0';
else
hi = (hic - 'A') + 10;
if (loc <= '9')
lo = loc - '0';
else
lo = (loc - 'A') + 10;
value->val.string.buff[j++] = hi * 16 + lo;
}
value->val.string.len = j;
}
else
memcpy(value->val.string.buff, str_start, value->val.string.len);
}
return buff;
}
static struct buffer query_parse_value(
char *s, size_t len,
struct buffer buff,
struct rmsgpack_dom_value *value, const char **error)
{
buff = query_chomp(buff);
if (query_peek(buff, "nil", STRLEN_CONST("nil")))
{
buff.offset += STRLEN_CONST("nil");
value->type = RDT_NULL;
}
else if (query_peek(buff, "true", STRLEN_CONST("true")))
{
buff.offset += STRLEN_CONST("true");
value->type = RDT_BOOL;
value->val.bool_ = 1;
}
else if (query_peek(buff, "false", STRLEN_CONST("false")))
{
buff.offset += STRLEN_CONST("false");
value->type = RDT_BOOL;
value->val.bool_ = 0;
}
else if (
query_peek(buff, "b", STRLEN_CONST("b")) ||
query_peek(buff, "\"", STRLEN_CONST("\"")) ||
query_peek(buff, "'", STRLEN_CONST("'")))
buff = query_parse_string(s, len,
buff, value, error);
else if (ISDIGIT((int)buff.data[buff.offset]))
buff = query_parse_integer(s, len, buff, value, error);
return buff;
}
static void query_peek_char(char *s, size_t len,
struct buffer buff, char *c,
const char **error)
{
if (query_is_eot(buff))
{
snprintf(s, len,
"%" PRIu64 "::Unexpected EOF",
(uint64_t)buff.offset
);
*error = s;
return;
}
*c = buff.data[buff.offset];
}
static struct buffer query_get_ident(
char *s, size_t _len,
struct buffer buff,
const char **ident,
size_t *len, const char **error)
{
char c = '\0';
if (query_is_eot(buff))
{
snprintf(s, _len,
"%" PRIu64 "::Unexpected EOF",
(uint64_t)buff.offset
);
*error = s;
return buff;
}
*ident = buff.data + buff.offset;
*len = 0;
query_peek_char(s, _len, buff, &c, error);
if (*error || !ISALPHA((int)c))
return buff;
buff.offset++;
*len = *len + 1;
query_peek_char(s, _len, buff, &c, error);
while (!*error)
{
if (!(ISALNUM((int)c) || c == '_'))
break;
buff.offset++;
*len = *len + 1;
query_peek_char(s, _len, buff, &c, error);
}
return buff;
}
static struct buffer query_expect_char(
char *s, size_t len,
struct buffer buff,
char c, const char ** error)
{
if ((unsigned)buff.offset >= buff.len)
{
snprintf(s, len,
"%" PRIu64 "::Unexpected EOF",
(uint64_t)buff.offset
);
*error = s;
}
else if (buff.data[buff.offset] != c)
{
snprintf(s, len,
"%" PRIu64 "::Expected '%c' found '%c'",
(uint64_t)buff.offset, c, buff.data[buff.offset]);
*error = s;
}
else
buff.offset++;
return buff;
}
static struct buffer query_parse_argument(
char *s, size_t len,
struct buffer buff,
struct argument *arg, const char **error)
{
buff = query_chomp(buff);
if (
ISALPHA((int)buff.data[buff.offset])
&& !(
query_peek(buff, "nil", STRLEN_CONST("nil"))
|| query_peek(buff, "true", STRLEN_CONST("true"))
|| query_peek(buff, "false", STRLEN_CONST("false"))
|| query_peek(buff, "b\"", STRLEN_CONST("b\""))
|| query_peek(buff, "b'", STRLEN_CONST("b'"))
/* bin string prefix*/
)
)
{
arg->type = AT_FUNCTION;
buff = query_parse_method_call(s, len, buff,
&arg->a.invocation, error);
}
else if (query_peek(buff, "{", STRLEN_CONST("{")))
{
arg->type = AT_FUNCTION;
buff = query_parse_table(s, len,
buff, &arg->a.invocation, error);
}
else
{
arg->type = AT_VALUE;
buff = query_parse_value(s,
len, buff, &arg->a.value, error);
}
return buff;
}
static struct buffer query_parse_method_call(
char *s, size_t len, struct buffer buff,
struct invocation *invocation, const char **error)
{
size_t func_name_len;
unsigned i;
struct argument args[QUERY_MAX_ARGS];
unsigned argi = 0;
const char *func_name = NULL;
struct registered_func *rf = registered_functions;
invocation->func = NULL;
buff = query_get_ident(s, len,
buff, &func_name, &func_name_len, error);
if (*error)
goto clean;
buff = query_chomp(buff);
buff = query_expect_char(s, len, buff, '(', error);
if (*error)
goto clean;
while (rf->name)
{
if (strncmp(rf->name, func_name, func_name_len) == 0)
{
invocation->func = rf->func;
break;
}
rf++;
}
if (!invocation->func)
{
query_raise_unknown_function(s, len,
buff.offset, func_name,
func_name_len, error);
goto clean;
}
buff = query_chomp(buff);
while (!query_peek(buff, ")", STRLEN_CONST(")")))
{
if (argi >= QUERY_MAX_ARGS)
{
strcpy_literal(s,
"Too many arguments in function call.");
*error = s;
goto clean;
}
buff = query_parse_argument(s, len, buff, &args[argi], error);
if (*error)
goto clean;
argi++;
buff = query_chomp(buff);
buff = query_expect_char(s, len, buff, ',', error);
if (*error)
{
*error = NULL;
break;
}
buff = query_chomp(buff);
}
buff = query_expect_char(s, len, buff, ')', error);
if (*error)
goto clean;
invocation->argc = argi;
invocation->argv = (argi > 0) ? (struct argument*)
malloc(sizeof(struct argument) * argi) : NULL;
if (!invocation->argv)
{
strcpy_literal(s, "Out of memory");
*error = s;
goto clean;
}
memcpy(invocation->argv, args,
sizeof(struct argument) * argi);
return buff;
clean:
for (i = 0; i < argi; i++)
query_argument_free(&args[i]);
return buff;
}
static struct rmsgpack_dom_value query_func_all_map(
struct rmsgpack_dom_value input,
unsigned argc, const struct argument *argv)
{
unsigned i;
struct argument arg;
struct rmsgpack_dom_value res;
struct rmsgpack_dom_value nil_value;
struct rmsgpack_dom_value *value = NULL;
res.type = RDT_BOOL;
res.val.bool_ = 1;
nil_value.type = RDT_NULL;
if (argc % 2 != 0)
{
res.val.bool_ = 0;
return res;
}
if (input.type != RDT_MAP)
return res;
for (i = 0; i < argc; i += 2)
{
arg = argv[i];
if (arg.type != AT_VALUE)
{
res.val.bool_ = 0;
return res;
}
value = rmsgpack_dom_value_map_value(&input, &arg.a.value);
if (!value) /* All missing fields are nil */
value = &nil_value;
arg = argv[i + 1];
if (arg.type == AT_VALUE)
res = func_equals(*value, 1, &arg);
else
{
res = query_func_is_true(arg.a.invocation.func(
*value,
arg.a.invocation.argc,
arg.a.invocation.argv
), 0, NULL);
value = NULL;
}
if (!res.val.bool_)
break;
}
return res;
}
static struct buffer query_parse_table(
char *s, size_t len,
struct buffer buff,
struct invocation *invocation, const char **error)
{
unsigned i;
size_t ident_len;
struct argument args[QUERY_MAX_ARGS];
const char *ident_name = NULL;
unsigned argi = 0;
buff = query_chomp(buff);
buff = query_expect_char(s, len, buff, '{', error);
if (*error)
goto clean;
buff = query_chomp(buff);
while (!query_peek(buff, "}", STRLEN_CONST("}")))
{
if (argi >= QUERY_MAX_ARGS)
{
strcpy_literal(s,
"Too many arguments in function call.");
*error = s;
goto clean;
}
if (ISALPHA((int)buff.data[buff.offset]))
{
buff = query_get_ident(s, len,
buff, &ident_name, &ident_len, error);
if (!*error)
{
args[argi].a.value.type = RDT_STRING;
args[argi].a.value.val.string.len = (uint32_t)ident_len;
args[argi].a.value.val.string.buff = (char*)calloc(
ident_len + 1,
sizeof(char)
);
if (!args[argi].a.value.val.string.buff)
goto clean;
strncpy(
args[argi].a.value.val.string.buff,
ident_name,
ident_len
);
}
}
else
buff = query_parse_string(s, len,
buff, &args[argi].a.value, error);
if (*error)
goto clean;
args[argi].type = AT_VALUE;
buff = query_chomp(buff);
argi++;
buff = query_expect_char(s, len, buff, ':', error);
if (*error)
goto clean;
buff = query_chomp(buff);
if (argi >= QUERY_MAX_ARGS)
{
strcpy_literal(s,
"Too many arguments in function call.");
*error = s;
goto clean;
}
buff = query_parse_argument(s, len, buff, &args[argi], error);
if (*error)
goto clean;
argi++;
buff = query_chomp(buff);
buff = query_expect_char(s, len, buff, ',', error);
if (*error)
{
*error = NULL;
break;
}
buff = query_chomp(buff);
}
buff = query_expect_char(s, len, buff, '}', error);
if (*error)
goto clean;
invocation->func = query_func_all_map;
invocation->argc = argi;
invocation->argv = (struct argument*)
malloc(sizeof(struct argument) * argi);
if (!invocation->argv)
{
strcpy_literal(s, "Out of memory");
*error = s;
goto clean;
}
memcpy(invocation->argv, args,
sizeof(struct argument) * argi);
return buff;
clean:
for (i = 0; i < argi; i++)
query_argument_free(&args[i]);
return buff;
}
void libretrodb_query_free(void *q)
{
unsigned i;
struct query *real_q = (struct query*)q;
real_q->ref_count--;
if (real_q->ref_count > 0)
return;
for (i = 0; i < real_q->root.argc; i++)
query_argument_free(&real_q->root.argv[i]);
free(real_q->root.argv);
real_q->root.argv = NULL;
real_q->root.argc = 0;
free(real_q);
}
void *libretrodb_query_compile(libretrodb_t *db,
const char *query, size_t buff_len, const char **error_string)
{
struct buffer buff;
/* TODO/FIXME - static local variable */
static char tmp_error_buff [MAX_ERROR_LEN] = {0};
struct query *q = (struct query*)malloc(sizeof(*q));
size_t error_buff_len = sizeof(tmp_error_buff);
if (!q)
return NULL;
q->ref_count = 1;
q->root.argc = 0;
q->root.func = NULL;
q->root.argv = NULL;
buff.data = query;
buff.len = buff_len;
buff.offset = 0;
*error_string = NULL;
buff = query_chomp(buff);
if (query_peek(buff, "{", STRLEN_CONST("{")))
{
buff = query_parse_table(tmp_error_buff,
error_buff_len, buff, &q->root, error_string);
if (*error_string)
goto error;
}
else if (ISALPHA((int)buff.data[buff.offset]))
buff = query_parse_method_call(tmp_error_buff,
error_buff_len,
buff, &q->root, error_string);
buff = query_expect_eof(tmp_error_buff,
error_buff_len,
buff, error_string);
if (*error_string)
goto error;
if (!q->root.func)
{
snprintf(tmp_error_buff, error_buff_len,
"%" PRIu64 "::Unexpected EOF",
(uint64_t)buff.offset
);
*error_string = tmp_error_buff;
goto error;
}
return q;
error:
if (q)
libretrodb_query_free(q);
return NULL;
}
void libretrodb_query_inc_ref(libretrodb_query_t *q)
{
struct query *rq = (struct query*)q;
if (rq)
rq->ref_count += 1;
}
int libretrodb_query_filter(libretrodb_query_t *q,
struct rmsgpack_dom_value *v)
{
struct invocation inv = ((struct query *)q)->root;
struct rmsgpack_dom_value res = inv.func(*v, inv.argc, inv.argv);
return (res.type == RDT_BOOL && res.val.bool_);
}