libidav/davql.c

Tue, 02 Jun 2015 10:07:20 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 02 Jun 2015 10:07:20 +0200
changeset 127
7072a2b4ae35
parent 75
56962faf2b42
permissions
-rw-r--r--

renamed GET to SELECT in davql syntax

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2015 Olaf Wintermann. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "davql.h"
#include "methods.h"
#include "utils.h"

DavQuery dav_ql_parse(char *query, va_list ap) {
    DavQuery davquery;
    davquery.command = DAV_QUERY_ERROR;
    davquery.command_data = NULL;
    sstr_t q = sstr(query);
    q = sstrtrim(q);
    
    // get query command
    sstr_t cmd;
    cmd.ptr = NULL;
    int i;
    for(i=0;i<q.length;i++) {
        if(q.ptr[i] == ' ') {
            cmd = sstrsubsl(q, 0, i);
            break;
        }
    }
    if(!cmd.ptr) {
        fprintf(stderr, "DQL syntax error\n");
        return davquery;
    }
    
    cmd = sstrtrim(cmd);
    q = sstrtrim(sstrsubs(q, i));
    if(!sstrcmp(cmd, S("get"))) {
        davquery.command = DAV_QUERY_GET;
        davquery.command_data = dav_ql_parse_get(q, ap);
    }
    
    return davquery;
}

DavGetQuery* dav_ql_parse_get(sstr_t q, va_list ap) {  
    sstr_t property_query = q;
    q = util_getsubstr_until_token(q, S("from"), &property_query);
    
    sstr_t from_query = q;
    sstr_t cond = util_getsubstr_until_token(q, S("where"), &from_query);
    sstr_t with = util_getsubstr_until_token(cond, S("with"), &cond);
    int depth = 1;
       
    // insert variable values
    UcxBuffer *fbuf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
    int var = 0;
    for(int i=0;i<from_query.length;i++) {
        char c = from_query.ptr[i];
        if(c == '%') {
            if(var) {
                ucx_buffer_putc(fbuf, '%'); // previous '%'
            } else {
                var = 1;
            }
        } else if(var) {
            switch(c) {
                case 's': {
                    char *arg = va_arg(ap, char*);
                    ucx_buffer_puts(fbuf, arg);
                    break;
                }
                default: {
                    ucx_buffer_putc(fbuf, '%');
                    ucx_buffer_putc(fbuf, c);
                }
            }
            var = 0;
        } else {
            ucx_buffer_putc(fbuf, c);
        }
    }
    
    // condition
    DavQOp *condition = NULL;
    size_t oplen = 0;
    if(cond.ptr) {
        //printf("cond: {%.*s}\n", cond.length, cond.ptr);
        UcxList *ops = NULL;
        if(dav_parse_condition(&ops, cond, ap)) {
            // TODO: error
            printf("parse error\n");
            return NULL;
        }
        oplen = ucx_list_size(ops);
        condition = calloc(sizeof(DavQOp), oplen);
        int l = 0;
        UCX_FOREACH(elm, ops) {
            condition[l] = *(DavQOp*)elm->data;
            free(elm->data);
            l++;
        }
        ucx_list_free(ops);
    }
    
    // with
    if(with.ptr) {
        if(dav_parse_with(with, &depth, ap)) {
            // TODO: error
            printf("parse error\n");
            return NULL;
        }
    }
    
    DavGetQuery *getquery = malloc(sizeof(DavGetQuery));
    getquery->properties = sstrdup(property_query);
    getquery->from = sstrdup(sstrn(fbuf->space, fbuf->pos));
    getquery->depth = depth;
    if(condition) {
        getquery->condition = condition;
        getquery->condlen = oplen;
    } else {
        getquery->condition = NULL;
        getquery->condlen = 0;
    }
    
    ucx_buffer_free(fbuf);
    return getquery;
}

void free_get_query(DavGetQuery *q) {
    free(q->from.ptr);
    free(q->properties.ptr);
    if(q->condition) {
        free(q->condition);
    }
    free(q);
}

int parse_path_query(sstr_t query, char **path, int *depth) {
    if(query.length == 1) {
        if(query.ptr[0] == '/') {
            *path = sstrdup(query).ptr;
            *depth = 1;
            return 0;
        } else {
            *path = NULL;
            return 1;
        }
    }
    
    if(query.ptr[query.length-1] == '*') {
        *depth = -1;
        *path = sstrdup(sstrsubsl(query, 0, query.length-1)).ptr;
    } else {
        *path = sstrdup(query).ptr;
        *depth = 1;
    }
    
    return 0;
}

static int dav_str2depth(sstr_t str, int *depth) {
    if(!sstrcmp(str, S("infinity"))) {
        *depth = -1;
    } else {
        sstr_t cp = sstrdup(str); // terminate
        *depth = atoi(cp.ptr);
        free(cp.ptr);
    }
    return 0;
}

int dav_parse_with(sstr_t with, int *depth, va_list ap) {
    int i;
    for(i=0;i<with.length;i++) {
        if(with.ptr[i] == ' ') {
            break;
        }
    }
    
    sstr_t name = sstrsubsl(with, 0, i);
    sstr_t value = sstrtrim(sstrsubs(with, i));
    //printf("with {%.*s} {%.*s}\n", name.length, name.ptr, value.length, value.ptr);
    
    if(!sstrcmp(name, S("depth"))) {
        if(value.length == 0) {
            return 1;
        } else if(value.ptr[0] == '%') {
            switch(value.ptr[1]) {
                default: return 1;
                case 's': {
                    sstr_t v = sstr(va_arg(ap, char*));
                    if(dav_str2depth(value, depth)) {
                        return 1;
                    }
                    break;
                }
                case 'd': {
                    *depth = va_arg(ap, int);
                    break;
                }
            }
        } else {
            if(dav_str2depth(value, depth)) {
                return 1;
            }
        }
    }
    
    return 0;
}

int dav_parse_condition(UcxList **ops, sstr_t cond, va_list ap) {
    sstr_t token;
    DavQOp *op1 = NULL; // level 1 operator
    DavQOp *op2 = NULL; // level 2 operator
    DavQOp *op3 = NULL; // level 3 operator
    while((token = condition_parser_next_token(&cond)).length > 0) {
        //printf("token: {%.*s}[%d]\n", token.length, token.ptr, token.length);
        int64_t type = 0;
        int tkop = condition_operator_type(token, &type);
        DavQOp *operation = malloc(sizeof(DavQOp));
        if(tkop > 0) {
            // operator token
            operation->type = DAVQOP_OPERATOR;
            operation->val = NULL;
            operation->intval = type;
            switch(tkop) {
                case 1: {
                    // operators: + - / * not
                    // add operation after next non operator token
                    op1 = operation;
                    break;
                }
                case 2: {
                    // operators: < > == != <= >=
                    if(op2) {
                        *ops = ucx_list_append(*ops, op2);
                    }
                    op2 = operation;
                    break;
                }
                case 3: {
                    // operators: and or xor
                    if(op2) {
                        *ops = ucx_list_append(*ops, op2);
                        op2 = NULL;
                    }
                    if(op3) {
                        *ops = ucx_list_append(*ops, op3);
                    }
                    op3 = operation;
                    break;
                }
            }
        } else {
            if(token.ptr[0] == '"' || token.ptr[0] == '\'') {
                operation->type = DAVQOP_STRING;
                operation->val = token.ptr+1;
                operation->intval = token.length-2;
            } else if(!sstrcmp(token, S("true")) ||
                    !sstrcmp(token, S("false")))
            {
                operation->type = DAVQOP_INTEGER;
                operation->val = NULL;
                operation->intval = util_getboolean(token.ptr);
            } else if(token.length == 2 && token.ptr[0] == '%') {
                switch(token.ptr[1]) {
                    case 's': {
                        char *arg = va_arg(ap, char*);
                        operation->type = DAVQOP_STRING;
                        operation->val = arg;
                        operation->intval = strlen(arg);
                        break;
                    }
                    case 'd': {
                        operation->type = DAVQOP_INTEGER;
                        operation->val = NULL;
                        operation->intval = va_arg(ap, int);
                        break;
                    }
                    case 't': {
                        operation->type = DAVQOP_INTEGER;
                        operation->val = NULL;
                        operation->intval = va_arg(ap, time_t);
                        break;
                    }
                    default: {
                        operation->type = DAVQOP_STRING;
                        operation->val = token.ptr;
                        operation->intval = token.length;
                    }
                }
            } else {
                sstr_t d = sstrdup(token);
                int64_t val = 0;
                int intval = util_strtoint(d.ptr, &val);
                free(d.ptr);
                if(intval) {
                    operation->type = DAVQOP_INTEGER;
                    operation->val = NULL;
                    operation->intval = val;
                } else {
                    if(!sstrcmp(token, S("contentlength"))) {
                        operation->type = DAVQOP_RESPROP;
                    } else if(!sstrcmp(token, S("lastmodified"))) {
                        operation->type = DAVQOP_RESPROP;
                    } else if(!sstrcmp(token, S("creationdate"))) {
                        operation->type = DAVQOP_RESPROP;
                    } else if(!sstrcmp(token, S("name"))) {
                        operation->type = DAVQOP_RESPROP;
                    } else if(!sstrcmp(token, S("path"))) {
                        operation->type = DAVQOP_RESPROP;
                    } else if(!sstrcmp(token, S("iscollection"))) {
                        operation->type = DAVQOP_RESPROP;
                    } else {
                        operation->type = DAVQOP_PROPERTY;
                    }
                    operation->val = token.ptr;
                    operation->intval = token.length;
                }
            }
            
            // add operation
            *ops = ucx_list_append(*ops, operation);
            if(op1) {
                // add level 1 operator
                *ops = ucx_list_append(*ops, op1);
                op1 = NULL;
            }
        }
    }
    if(op1) {
        *ops = ucx_list_append(*ops, op1);
    }
    if(op2) {
        *ops = ucx_list_append(*ops, op2);
    }
    if(op3) {
        *ops = ucx_list_append(*ops, op3);
    }
    return 0;
}

sstr_t condition_parser_next_token(sstr_t *str) {
    sstr_t s = *str;
    sstr_t t;
    t.ptr = NULL;
    t.length = 0;
    // remove leading space
    int i;
    for(i=0;i<s.length;i++) {
        if(s.ptr[i] > 32) {
            break;
        }
    }
    s.length -= i;
    s.ptr += i;
    
    if(s.length == 0) {
        *str = s;
        return t;
    }
    
    // check for single char operators
    switch(s.ptr[0]) {
        case '<':
        case '>':
        case '+':
        case '-':
        case '*':
        case '/': {
            t.ptr = s.ptr;
            t.length = 1;
            str->ptr = s.ptr + 1;
            str->length = s.length - 1;
            return t;
        }
    }
    
    if(s.length > 1) {
        // check for double char operators
        int16_t op = *(int16_t*)s.ptr;
        if(op == '==' || op == '!=' || op == '>=' || op == '=<') {
            t.ptr = s.ptr;
            t.length = 2;
            str->ptr = s.ptr + 2;
            str->length = s.length - 2;
            return t;
        }
    } else {
        t.ptr = s.ptr;
        t.length = 1;
        str->ptr = s.ptr + 1;
        str->length = s.length - 1;
        return t;
    }
    
    // TODO: brackets
    
    // check for string literal
    if(s.ptr[0] == '\'' || s.ptr[0] == '"') {
        for(i=1;i<s.length;i++) {
            if(s.ptr[0] == s.ptr[i]) {
                i++;
                break;
            }
        }
        t.ptr = s.ptr;
        t.length = i;
        str->ptr = s.ptr + i;
        str->length = s.length - i;
        return t;
    }
    
    for(i=0;i<s.length;i++) {
        char c = s.ptr[i];
        if((c < 33) || (c > 41 && c < 48) || (c > 59 && c < 63)) {
            break;
        }
    }
    t.ptr = s.ptr;
    t.length = i;
    str->ptr = s.ptr + i;
    str->length = s.length - i;
    return t;
}

int condition_operator_type(sstr_t token, int64_t *type) {
    // returns the operator level and sets the type
    
    if(token.ptr[0] == '"' || token.ptr[0] == '\'' || token.ptr[0] == '(') {
        return 0;
    }
    
    if(token.length == 1) {
        switch(token.ptr[0]) {
            case '+': *type = 1; return 1;
            case '-': *type = 2; return 1;
            case '*': *type = 3; return 1;
            case '/': *type = 4; return 1;
            case '<': *type = 5; return 2;
            case '>': *type = 6; return 2;
        }
    }
    if(!sstrcmp(token, S("not"))) {
        *type = 0;
        return 1;
    }
    
    if(!sstrcmp(token, S("=="))) {
        *type = 7;
        return 2;
    }
    if(!sstrcmp(token, S("!="))) {
        *type = 8;
        return 2;
    }
    if(!sstrcmp(token, S("<="))) {
        *type = 9;
        return 2;
    }
    if(!sstrcmp(token, S(">="))) {
        *type = 10;
        return 2;
    }
    
    if(!sstrcmp(token, S("and"))) {
        *type = 11;
        return 3;
    }
    if(!sstrcmp(token, S("or"))) {
        *type = 12;
        return 3;
    }
    if(!sstrcmp(token, S("xor"))) {
        *type = 13;
        return 3;
    }
    
    return 0;
}

int condition_eval(DavResource *res, DavQOp *cond, size_t len) {
    DavQOp stack[128];
    int stackpos = 0;
    for(int i=0;i<len;i++) {
        DavQOp op = cond[i];
        switch(op.type) {
            case DAVQOP_OPERATOR: {
                if(op.intval == 0) {
                    // not operator
                    if(stackpos < 1) {
                        // error
                        printf("no data on stack\n");
                        return 0;
                    }
                    int pos = stackpos-1;
                    if(stack[pos].type == DAVQOP_INTEGER) {
                        //printf("not %" PRId64 "\n", stack[pos].intval);
                        stack[pos].intval = !stack[pos].intval;
                    } else {
                        // error
                        printf("wrong value for 'not' operator\n");
                        return 0;
                    }
                } else {
                    DavQOp val1 = stack[stackpos-2];
                    DavQOp val2 = stack[stackpos-1];
                    DavQOp result;
                    if(val1.type == DAVQOP_INTEGER) {
                        if(val2.type == DAVQOP_INTEGER) {
                            result = compare_intint(
                                    op.intval,
                                    val1.intval,
                                    val2.intval);
                        } else {
                            result = compare_intstr(op.intval, val1, val2);
                        }
                    } else {
                        if(val2.type == DAVQOP_INTEGER) {
                            result = compare_strint(op.intval, val1, val2);
                        } else {
                            result = compare_strstr(op.intval, val1, val2);
                        }
                    } 
                    stack[stackpos-2] = result;
                    stackpos--;
                }
                break;
            }
            case DAVQOP_STRING:
            case DAVQOP_INTEGER:
            case DAVQOP_TIME: {
                if(op.type == DAVQOP_STRING) {
                    //printf("put on stack: '%s'\n", op.val);
                } else {
                    //printf("put on stack[%d]: %" PRId64 "\n", stackpos, op.intval);
                }
                stack[stackpos++] = op;
                break;
            }
            case DAVQOP_PROPERTY: {
                sstr_t pname = sstrn(op.val, op.intval);
                pname = sstrdup(pname);
                char *property_value = dav_get_property(res, pname.ptr);
                free(pname.ptr);
                DavQOp value;
                value.type = DAVQOP_STRING;
                if(property_value) {
                    //printf("put on stack: \"%s\"\n", property_value);
                    value.val = property_value;
                    value.intval = strlen(property_value);
                } else {
                    //printf("put on stack: null string\n");
                    value.val = NULL;
                    value.intval = 0;
                }
                stack[stackpos++] = value;
                break;
            }
            case DAVQOP_RESPROP: {
                sstr_t name = sstrn(op.val, op.intval);
                DavQOp value;
                value.type = DAVQOP_INTEGER;
                value.val = NULL;
                if(!sstrcmp(name, S("contentlength"))) {
                    //printf("put contentlength\n");
                    value.intval = res->contentlength;
                } else if(!sstrcmp(name, S("lastmodified"))) {
                    //printf("put getlastmodified\n");
                    value.intval = res->lastmodified;
                } else if(!sstrcmp(name, S("creationdate"))) {
                    value.intval = res->creationdate;
                } else if(!sstrcmp(name, S("name"))) {
                    value.type = DAVQOP_STRING;
                    value.val = res->name;
                    value.intval = strlen(res->name);
                } else if(!sstrcmp(name, S("path"))) {
                    value.type = DAVQOP_STRING;
                    value.val = res->path;
                    value.intval = strlen(res->path);
                } else if(!sstrcmp(name, S("iscollection"))) {
                    value.type = DAVQOP_INTEGER;
                    value.val = NULL;
                    value.intval = res->iscollection;
                }
                stack[stackpos++] = value;
                break;
            }
        }
    }
    if(stackpos != 1) {
        return 0;
    }
    DavQOp result = stack[0];
    //printf("result: %" PRId64 "\n", result.intval);
    return (int)result.intval;
}

DavQOp compare_intint(int op, int64_t v1, int64_t v2) {
    DavQOp res;
    res.type = DAVQOP_INTEGER;
    res.val = NULL;
    res.intval = 0;
    switch(op) {
        case 5: {
            // <
            //printf("compare: %" PRId64 " < %" PRId64 "\n", v1, v2);
            res.intval = v1 < v2;
            break;
        }
        case 6: {
            // >
            //printf("compare: %" PRId64 " > %" PRId64 "\n", v1, v2);
            res.intval = v1 > v2;
            break;
        }
        case 7: {
            // ==
            //printf("compare: %" PRId64 " == %" PRId64 "\n", v1, v2);
            res.intval = v1 == v2;
            break;
        }
        case 8: {
            // !=
            //printf("compare: %" PRId64 " != %" PRId64 "\n", v1, v2);
            res.intval = v1 != v2;
            break;
        }
        case 9: {
            // <=
            //printf("compare: %" PRId64 " <= %" PRId64 "\n", v1, v2);
            res.intval = v1 <= v2;
            break;
        }
        case 10: {
            // >=
            //printf("compare: %" PRId64 " >= %" PRId64 "\n", v1, v2);
            res.intval = v1 >= v2;
            break;
        }
        case 11: {
            // and
            //printf("compare: %" PRId64 " and %" PRId64 "\n", v1, v2);
            res.intval = v1 && v2;
            break;
        }
        case 12: {
            // or
            //printf("compare: %" PRId64 " or %" PRId64 "\n", v1, v2);
            res.intval = v1 || v2;
            break;
        }
        case 13: {
            // xor
            //printf("compare: %" PRId64 " xor %" PRId64 "\n", v1, v2);
            res.intval = v1 ^ v2;
            break;
        }
    }
    return res;
}

DavQOp compare_strint(int op, DavQOp v1, DavQOp v2) {
    int64_t v1int;
    sstr_t s1 = sstrn(v1.val, v1.intval);
    s1 = sstrdup(s1);
    if(util_strtoint(s1.ptr, &v1int)) {
        free(s1.ptr);
        return compare_intint(op, v1int, v2.intval);
    } else {
        free(s1.ptr);
        // TODO
    }
}

DavQOp compare_intstr(int op, DavQOp v1, DavQOp v2) {
    // TODO
}

DavQOp compare_strstr(int op, DavQOp v1, DavQOp v2) {
    DavQOp res;
    res.type = DAVQOP_INTEGER;
    res.val = NULL;
    res.intval = 0;
    sstr_t s1 = sstrn(v1.val, v1.intval);
    sstr_t s2 = sstrn(v2.val, v2.intval);
    switch(op) {
        case 5: {
            // <
            //printf("str compare: %.*s < %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = s1.length < s2.length;
            break;
        }
        case 6: {
            // >
            //printf("str compare: %.*s > %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = s1.length > s2.length;
            break;
        }
        case 7: {
            // ==
            //printf("str compare: %.*s == %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = sstrcmp(s1, s2) == 0;
            break;
        }
        case 8: {
            // !=
            //printf("str compare: %.*s != %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = sstrcmp(s1, s2) != 0;
            break;
        }
        case 9: {
            // <=
            //printf("str compare: %.*s <= %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = s1.length <= s2.length;
            break;
        }
        case 10: {
            // >=
            //printf("str compare: %.*s >= %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = s1.length >= s2.length;
            break;
        }
        case 11: {
            // and
            //printf("str compare: %.*s and %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = s1.ptr && s2.ptr;
            break;
        }
        case 12: {
            // or
            //printf("str compare: %.*s or %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = s1.ptr || s2.ptr;
            break;
        }
        case 13: {
            // xor
            //printf("str compare: %.*s xor %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
            res.intval = (intptr_t)s1.ptr ^ (intptr_t)s2.ptr;
            break;
        }
    }
    return res;
}

mercurial