contiki/apps/antelope/aql-parser.c

857 lines
15 KiB
C

/*
* Copyright (c) 2010, Swedish Institute of Computer Science
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/**
* \file
* A recursive parser for AQL, the Antelope Query Language.
* \author
* Nicolas Tsiftes <nvt@sics.se>
*/
#include "attribute.h"
#include "db-options.h"
#include "index.h"
#include "aql.h"
#include "lvm.h"
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define DEBUG DEBUG_NONE
#include "debug.h"
#if DEBUG
static char error_message[DB_ERROR_BUF_SIZE];
static int error_line;
static const char *error_function;
#define RETURN(value) \
do { \
if(error_message[0] == '\0') { \
strncpy(error_message, lexer->input, sizeof(error_message) - 1); \
error_line = __LINE__; \
error_function = __func__; \
} \
} while(0); \
return (value)
#define RESET_ERROR() \
do { \
error_message[0] = '\0'; \
error_line = 0; \
error_function = NULL; \
} while(0)
#else
#define RETURN(value) return (value)
#define RESET_ERROR()
#endif
#define PARSER(name) \
static aql_status_t \
parse_##name(lexer_t *lexer)
#define PARSER_ARG(name, arg) \
static aql_status_t \
parse_##name(lexer_t *lexer, arg)
#define PARSER_TOKEN(name) \
static token_t \
parse_##name(lexer_t *lexer)
#define PARSE(name) \
!AQL_ERROR(parse_##name(lexer))
#define PARSE_TOKEN(name) \
parse_##name(lexer)
#define NEXT lexer_next(lexer)
#define REWIND lexer_rewind(lexer); RESET_ERROR()
#define TOKEN *lexer->token
#define VALUE *lexer->value
#define CONSUME(token) \
do { \
NEXT; \
if(TOKEN != (token)) { \
RETURN(SYNTAX_ERROR); \
} \
} while(0)
/* The parsing of AQL results in this aql_adt_t object. */
static aql_adt_t *adt;
/* Conditional statements are compiled into VM bytecode, which is stored in
an instance of the LogicVM. */
static lvm_instance_t p;
static unsigned char vmcode[DB_VM_BYTECODE_SIZE];
/* Parsing functions for AQL. */
PARSER_TOKEN(cmp)
{
NEXT;
switch(TOKEN) {
case EQUAL:
case NOT_EQUAL:
case GT:
case LT:
case GEQ:
case LEQ:
return TOKEN;
default:
return NONE;
}
}
PARSER_TOKEN(op)
{
NEXT;
switch(TOKEN) {
case ADD:
case SUB:
case MUL:
case DIV:
case RIGHT_PAREN:
return TOKEN;
default:
return NONE;
}
}
PARSER_TOKEN(aggregator)
{
NEXT;
switch(TOKEN) {
case COUNT:
case SUM:
case MEAN:
case MEDIAN:
case MAX:
case MIN:
return TOKEN;
default:
return NONE;
}
}
PARSER(attributes)
{
token_t token;
aql_aggregator_t function;
token = PARSE_TOKEN(aggregator);
if(token != NONE) {
switch(TOKEN) {
case COUNT:
function = AQL_COUNT;
break;
case SUM:
function = AQL_SUM;
break;
case MEAN:
function = AQL_MEAN;
break;
case MEDIAN:
function = AQL_MEDIAN;
break;
case MAX:
function = AQL_MAX;
break;
case MIN:
function = AQL_MIN;
break;
default:
RETURN(SYNTAX_ERROR);
}
AQL_SET_FLAG(adt, AQL_FLAG_AGGREGATE);
PRINTF("aggregator: %d\n", TOKEN);
/* Parse the attribute to aggregate. */
CONSUME(LEFT_PAREN);
CONSUME(IDENTIFIER);
AQL_ADD_AGGREGATE(adt, function, VALUE);
PRINTF("aggregated attribute: %s\n", VALUE);
CONSUME(RIGHT_PAREN);
goto check_more_attributes;
} else {
REWIND;
}
/* Plain identifier. */
CONSUME(IDENTIFIER);
AQL_ADD_ATTRIBUTE(adt, VALUE, DOMAIN_UNSPECIFIED, 0);
check_more_attributes:
NEXT;
if(TOKEN == COMMA) {
if(!PARSE(attributes)) {
RETURN(SYNTAX_ERROR);
}
} else {
REWIND;
}
RETURN(OK);
}
PARSER(relations)
{
/* Parse comma-separated identifiers for relations. */
CONSUME(IDENTIFIER);
AQL_ADD_RELATION(adt, VALUE);
NEXT;
if(TOKEN == COMMA) {
if(!PARSE(relations)) {
RETURN(SYNTAX_ERROR);
}
} else {
REWIND;
}
RETURN(OK);
}
PARSER(values)
{
/* Parse comma-separated attribute values. */
NEXT;
switch(TOKEN) {
case STRING_VALUE:
AQL_ADD_VALUE(adt, DOMAIN_STRING, VALUE);
break;
case INTEGER_VALUE:
AQL_ADD_VALUE(adt, DOMAIN_INT, VALUE);
break;
default:
RETURN(SYNTAX_ERROR);
}
NEXT;
if(TOKEN == COMMA) {
return PARSE(values);
} else {
REWIND;
}
RETURN(OK);
}
PARSER(operand)
{
NEXT;
switch(TOKEN) {
case IDENTIFIER:
lvm_register_variable(VALUE, LVM_LONG);
lvm_set_variable(&p, VALUE);
AQL_ADD_PROCESSING_ATTRIBUTE(adt, VALUE);
break;
case STRING_VALUE:
break;
case FLOAT_VALUE:
break;
case INTEGER_VALUE:
lvm_set_long(&p, *(long *)lexer->value);
break;
default:
RETURN(SYNTAX_ERROR);
}
RETURN(OK);
}
PARSER(expr)
{
token_t token;
size_t saved_end;
operator_t op;
saved_end = lvm_get_end(&p);
NEXT;
if(TOKEN == LEFT_PAREN) {
if(!PARSE(expr)) {
RETURN(SYNTAX_ERROR);
}
CONSUME(RIGHT_PAREN);
} else {
REWIND;
if(!PARSE(operand)) {
RETURN(SYNTAX_ERROR);
}
}
while(1) {
token = PARSE_TOKEN(op);
if(token == NONE) {
saved_end = lvm_get_end(&p);
REWIND;
break;
} else if (token == RIGHT_PAREN) {
break;
}
if(!PARSE(operand) && !PARSE(expr)) {
RETURN(SYNTAX_ERROR);
}
saved_end = lvm_shift_for_operator(&p, saved_end);
switch(token) {
case ADD:
op = LVM_ADD;
break;
case SUB:
op = LVM_SUB;
break;
case MUL:
op = LVM_MUL;
break;
case DIV:
op = LVM_DIV;
break;
default:
RETURN(SYNTAX_ERROR);
}
lvm_set_op(&p, op);
lvm_set_end(&p, saved_end);
}
return OK;
}
PARSER(comparison)
{
token_t token;
size_t saved_end;
operator_t rel;
saved_end = lvm_jump_to_operand(&p);
if(!PARSE(expr)) {
RETURN(SYNTAX_ERROR);
}
saved_end = lvm_set_end(&p, saved_end);
token = PARSE_TOKEN(cmp);
if(token == NONE) {
RETURN(SYNTAX_ERROR);
}
switch(token) {
case GT:
rel = LVM_GE;
break;
case GEQ:
rel = LVM_GEQ;
break;
case LT:
rel = LVM_LE;
break;
case LEQ:
rel = LVM_LEQ;
break;
case EQUAL:
rel = LVM_EQ;
break;
case NOT_EQUAL:
rel = LVM_NEQ;
break;
default:
RETURN(SYNTAX_ERROR);
}
lvm_set_relation(&p, rel);
lvm_set_end(&p, saved_end);
if(!PARSE(expr)) {
RETURN(SYNTAX_ERROR);
}
RETURN(OK);
}
PARSER(where)
{
int r;
operator_t connective;
size_t saved_end;
if(!PARSE(comparison)) {
RETURN(SYNTAX_ERROR);
}
saved_end = 0;
/* The WHERE clause can consist of multiple prepositions. */
for(;;) {
NEXT;
if(TOKEN != AND && TOKEN != OR) {
REWIND;
break;
}
connective = TOKEN == AND ? LVM_AND : LVM_OR;
saved_end = lvm_shift_for_operator(&p, saved_end);
lvm_set_relation(&p, connective);
lvm_set_end(&p, saved_end);
NEXT;
if(TOKEN == LEFT_PAREN) {
r = PARSE(where);
if(!r) {
RETURN(SYNTAX_ERROR);
}
CONSUME(RIGHT_PAREN);
} else {
REWIND;
r = PARSE(comparison);
if(!r) {
RETURN(r);
}
}
}
lvm_print_code(&p);
return OK;
}
PARSER(join)
{
AQL_SET_TYPE(adt, AQL_TYPE_JOIN);
CONSUME(IDENTIFIER);
PRINTF("Left relation: %s\n", VALUE);
AQL_ADD_RELATION(adt, VALUE);
CONSUME(COMMA);
CONSUME(IDENTIFIER);
PRINTF("Right relation: %s\n", VALUE);
AQL_ADD_RELATION(adt, VALUE);
CONSUME(ON);
CONSUME(IDENTIFIER);
PRINTF("Join on attribute %s\n", VALUE);
AQL_ADD_ATTRIBUTE(adt, VALUE, DOMAIN_UNSPECIFIED, 0);
CONSUME(PROJECT);
/* projection attributes... */
if(!PARSE(attributes)) {
RETURN(SYNTAX_ERROR);
}
CONSUME(END);
RETURN(OK);
}
PARSER(select)
{
AQL_SET_TYPE(adt, AQL_TYPE_SELECT);
/* projection attributes... */
if(!PARSE(attributes)) {
RETURN(SYNTAX_ERROR);
}
CONSUME(FROM);
if(!PARSE(relations)) {
RETURN(SYNTAX_ERROR);
}
NEXT;
if(TOKEN == WHERE) {
lvm_reset(&p, vmcode, sizeof(vmcode));
if(!PARSE(where)) {
RETURN(SYNTAX_ERROR);
}
AQL_SET_CONDITION(adt, &p);
} else {
REWIND;
RETURN(OK);
}
CONSUME(END);
return OK;
}
PARSER(insert)
{
AQL_SET_TYPE(adt, AQL_TYPE_INSERT);
CONSUME(LEFT_PAREN);
if(!PARSE(values)) {
RETURN(SYNTAX_ERROR);
}
CONSUME(RIGHT_PAREN);
CONSUME(INTO);
if(!PARSE(relations)) {
RETURN(SYNTAX_ERROR);
}
RETURN(OK);
}
PARSER(remove_attribute)
{
AQL_SET_TYPE(adt, AQL_TYPE_REMOVE_ATTRIBUTE);
CONSUME(IDENTIFIER);
AQL_ADD_RELATION(adt, VALUE);
CONSUME(DOT);
CONSUME(IDENTIFIER);
PRINTF("Removing the index for the attribute %s\n", VALUE);
AQL_ADD_ATTRIBUTE(adt, VALUE, DOMAIN_UNSPECIFIED, 0);
RETURN(OK);
}
#if DB_FEATURE_REMOVE
PARSER(remove_from)
{
AQL_SET_TYPE(adt, AQL_TYPE_REMOVE_TUPLES);
/* Use a temporary persistent relation to assign the query result to. */
AQL_SET_FLAG(adt, AQL_FLAG_ASSIGN);
AQL_ADD_RELATION(adt, TEMP_RELATION);
CONSUME(IDENTIFIER);
AQL_ADD_RELATION(adt, VALUE);
CONSUME(WHERE);
lvm_reset(&p, vmcode, sizeof(vmcode));
AQL_SET_CONDITION(adt, &p);
return PARSE(where);
}
#endif /* DB_FEATURE_REMOVE */
PARSER(remove_index)
{
AQL_SET_TYPE(adt, AQL_TYPE_REMOVE_INDEX);
CONSUME(IDENTIFIER);
AQL_ADD_RELATION(adt, VALUE);
CONSUME(DOT);
CONSUME(IDENTIFIER);
PRINTF("remove index: %s\n", VALUE);
AQL_ADD_ATTRIBUTE(adt, VALUE, DOMAIN_UNSPECIFIED, 0);
RETURN(OK);
}
PARSER(remove_relation)
{
AQL_SET_TYPE(adt, AQL_TYPE_REMOVE_RELATION);
CONSUME(IDENTIFIER);
PRINTF("remove relation: %s\n", VALUE);
AQL_ADD_RELATION(adt, VALUE);
RETURN(OK);
}
PARSER(remove)
{
aql_status_t r;
NEXT;
switch(TOKEN) {
case ATTRIBUTE:
r = PARSE(remove_attribute);
break;
#if DB_FEATURE_REMOVE
case FROM:
r = PARSE(remove_from);
break;
#endif
case INDEX:
r = PARSE(remove_index);
break;
case RELATION:
r = PARSE(remove_relation);
break;
default:
RETURN(SYNTAX_ERROR);
}
if(!r) {
RETURN(SYNTAX_ERROR);
}
CONSUME(END);
RETURN(OK);
}
PARSER_TOKEN(index_type)
{
index_type_t type;
NEXT;
switch(TOKEN) {
case INLINE:
type = INDEX_INLINE;
break;
case MAXHEAP:
type = INDEX_MAXHEAP;
break;
case MEMHASH:
type = INDEX_MEMHASH;
break;
default:
return NONE;
};
AQL_SET_INDEX_TYPE(adt, type);
return TOKEN;
}
PARSER(create_index)
{
AQL_SET_TYPE(adt, AQL_TYPE_CREATE_INDEX);
CONSUME(IDENTIFIER);
AQL_ADD_RELATION(adt, VALUE);
CONSUME(DOT);
CONSUME(IDENTIFIER);
PRINTF("Creating an index for the attribute %s\n", VALUE);
AQL_ADD_ATTRIBUTE(adt, VALUE, DOMAIN_UNSPECIFIED, 0);
CONSUME(TYPE);
if(PARSE_TOKEN(index_type) == NONE) {
RETURN(SYNTAX_ERROR);
}
RETURN(OK);
}
PARSER(create_relation)
{
CONSUME(IDENTIFIER);
AQL_SET_TYPE(adt, AQL_TYPE_CREATE_RELATION);
AQL_ADD_RELATION(adt, VALUE);
RETURN(OK);
}
PARSER_ARG(domain, char *name)
{
domain_t domain;
unsigned element_size;
NEXT;
switch(TOKEN) {
case STRING:
domain = DOMAIN_STRING;
/* Parse the amount of characters for this domain. */
CONSUME(LEFT_PAREN);
CONSUME(INTEGER_VALUE);
element_size = *(long *)lexer->value;
CONSUME(RIGHT_PAREN);
break;
case LONG:
domain = DOMAIN_LONG;
element_size = 4;
break;
case INT:
domain = DOMAIN_INT;
element_size = 2;
break;
default:
return NONE;
}
AQL_ADD_ATTRIBUTE(adt, name, domain, element_size);
return OK;
}
PARSER(create_attributes)
{
aql_status_t r;
char name[ATTRIBUTE_NAME_LENGTH];
AQL_SET_TYPE(adt, AQL_TYPE_CREATE_ATTRIBUTE);
CONSUME(IDENTIFIER);
strncpy(name, VALUE, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';
CONSUME(DOMAIN);
r = parse_domain(lexer, name);
if(AQL_ERROR(r)) {
RETURN(r);
}
CONSUME(IN);
CONSUME(IDENTIFIER);
AQL_ADD_RELATION(adt, VALUE);
RETURN(OK);
}
PARSER(create)
{
aql_status_t r;
NEXT;
switch(TOKEN) {
case ATTRIBUTE:
r = PARSE(create_attributes);
break;
case INDEX:
r = PARSE(create_index);
break;
case RELATION:
r = PARSE(create_relation);
break;
default:
RETURN(SYNTAX_ERROR);
}
if(!r) {
RETURN(SYNTAX_ERROR);
}
CONSUME(END);
RETURN(OK);
}
aql_status_t
aql_parse(aql_adt_t *external_adt, char *input_string)
{
lexer_t lex;
token_t token = NONE;
value_t value;
aql_status_t result;
RESET_ERROR();
PRINTF("Parsing \"%s\"\n", input_string);
adt = external_adt;
AQL_CLEAR(adt);
AQL_SET_CONDITION(adt, NULL);
lexer_start(&lex, input_string, &token, &value);
result = lexer_next(&lex);
if(!AQL_ERROR(result)) {
switch(token) {
case IDENTIFIER:
PRINTF("Assign the result to relation %s\n", *lex.value);
AQL_ADD_RELATION(adt, *lex.value);
AQL_SET_FLAG(adt, AQL_FLAG_ASSIGN);
if(AQL_ERROR(lexer_next(&lex))) {
result = SYNTAX_ERROR;
break;
}
if(*lex.token != ASSIGN) {
result = SYNTAX_ERROR;
break;
}
if(AQL_ERROR(lexer_next(&lex))) {
result = SYNTAX_ERROR;
break;
}
switch(*lex.token) {
case SELECT:
result = parse_select(&lex);
break;
case JOIN:
result = parse_join(&lex);
break;
default:
result = SYNTAX_ERROR;
}
break;
case JOIN:
result = parse_join(&lex);
break;
case CREATE:
result = parse_create(&lex);
break;
case REMOVE:
result = parse_remove(&lex);
break;
case INSERT:
result = parse_insert(&lex);
break;
case SELECT:
result = parse_select(&lex);
break;
case NONE:
case COMMENT:
result = OK;
case END:
break;
default:
result = SYNTAX_ERROR;
}
}
if(AQL_ERROR(result)) {
PRINTF("Error in function %s, line %d: input \"%s\"\n",
error_function, error_line, error_message);
}
return result;
}