+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 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 "davqlparser.h"
+#include <ucx/utils.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#define sfmtarg(s) ((int)(s).length), (s).ptr
+
+// ------------------------------------------------------------------------
+// D E B U G E R
+// ------------------------------------------------------------------------
+
+static const char* _map_querytype(davqltype_t type) {
+ switch(type) {
+ case DAVQL_ERROR: return "ERROR";
+ case DAVQL_SELECT: return "SELECT";
+ case DAVQL_SET: return "SET";
+ default: return "unknown";
+ }
+}
+
+static const char* _map_exprtype(davqlexprtype_t type) {
+ switch(type) {
+ case DAVQL_UNDEFINED_TYPE: return "undefined";
+ case DAVQL_NUMBER: return "NUMBER";
+ case DAVQL_STRING: return "STRING";
+ case DAVQL_TIMESTAMP: return "TIMESTAMP";
+ case DAVQL_IDENTIFIER: return "IDENTIFIER";
+ case DAVQL_UNARY: return "UNARY";
+ case DAVQL_BINARY: return "BINARY";
+ case DAVQL_LOGICAL: return "LOGICAL";
+ case DAVQL_FUNCCALL: return "FUNCCALL";
+ default: return "unknown";
+ }
+}
+
+static const char* _map_specialfield(int info) {
+ switch(info) {
+ case 0: return "";
+ case 1: return "with wildcard";
+ case 2: return "(resource data only)";
+ default: return "with mysterious identifier";
+ }
+}
+
+static const char* _map_operator(davqloperator_t op) {
+ // don't use string array, because enum values may change
+ switch(op) {
+ case DAVQL_NOOP: return "no operator";
+ case DAVQL_CALL: return "function call"; case DAVQL_ARGLIST: return ",";
+ case DAVQL_ADD: return "+"; case DAVQL_SUB: return "-";
+ case DAVQL_MUL: return "*"; case DAVQL_DIV: return "/";
+ case DAVQL_AND: return "&"; case DAVQL_OR: return "|";
+ case DAVQL_XOR: return "^"; case DAVQL_NEG: return "~";
+ case DAVQL_NOT: return "NOT"; case DAVQL_LAND: return "AND";
+ case DAVQL_LOR: return "OR"; case DAVQL_LXOR: return "XOR";
+ case DAVQL_EQ: return "="; case DAVQL_NEQ: return "!=";
+ case DAVQL_LT: return "<"; case DAVQL_GT: return ">";
+ case DAVQL_LE: return "<="; case DAVQL_GE: return ">=";
+ case DAVQL_LIKE: return "LIKE"; case DAVQL_UNLIKE: return "UNLIKE";
+ default: return "unknown";
+ }
+}
+
+static void dav_debug_ql_fnames_print(DavQLStatement *stmt) {
+ if (stmt->fields) {
+ printf("Field names: ");
+ UCX_FOREACH(field, stmt->fields) {
+ DavQLField *f = field->data;
+ printf("%.*s, ", sfmtarg(f->name));
+ }
+ printf("\b\b \b\b\n");
+ }
+}
+
+static void dav_debug_ql_stmt_print(DavQLStatement *stmt) {
+ // Basic information
+ size_t fieldcount = ucx_list_size(stmt->fields);
+ int specialfield = 0;
+ if (stmt->fields) {
+ DavQLField* firstfield = (DavQLField*)stmt->fields->data;
+ if (firstfield->expr->type == DAVQL_IDENTIFIER) {
+ switch (firstfield->expr->srctext.ptr[0]) {
+ case '*': specialfield = 1; break;
+ case '-': specialfield = 2; break;
+ }
+ }
+ }
+ if (specialfield) {
+ fieldcount--;
+ }
+ printf("Statement: %.*s\nType: %s\nField count: %zu %s\n",
+ sfmtarg(stmt->srctext),
+ _map_querytype(stmt->type),
+ fieldcount,
+ _map_specialfield(specialfield));
+
+ dav_debug_ql_fnames_print(stmt);
+ printf("Path: %.*s\nHas where clause: %s\n",
+ sfmtarg(stmt->path),
+ stmt->where ? "yes" : "no");
+
+ // WITH attributes
+ if (stmt->depth == DAV_DEPTH_INFINITY) {
+ printf("Depth: infinity\n");
+ } else if (stmt->depth == DAV_DEPTH_PLACEHOLDER) {
+ printf("Depth: placeholder\n");
+ } else {
+ printf("Depth: %d\n", stmt->depth);
+ }
+
+ // order by clause
+ printf("Order by: ");
+ if (stmt->orderby) {
+ UCX_FOREACH(crit, stmt->orderby) {
+ DavQLOrderCriterion *critdata = crit->data;
+ printf("%.*s %s%s", sfmtarg(critdata->column->srctext),
+ critdata->descending ? "desc" : "asc",
+ crit->next ? ", " : "\n");
+ }
+ } else {
+ printf("nothing\n");
+ }
+
+ // error messages
+ if (stmt->errorcode) {
+ printf("\nError code: %d\nError: %s\n",
+ stmt->errorcode, stmt->errormessage);
+ }
+}
+
+static int dav_debug_ql_expr_selected(DavQLExpression *expr) {
+ if (!expr) {
+ printf("Currently no expression selected.\n");
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static void dav_debug_ql_expr_print(DavQLExpression *expr) {
+ if (dav_debug_ql_expr_selected(expr)) {
+ sstr_t empty = ST("(empty)");
+ printf(
+ "Text: %.*s\nType: %s\nOperator: %s\n",
+ sfmtarg(expr->srctext),
+ _map_exprtype(expr->type),
+ _map_operator(expr->op));
+ if (expr->left || expr->right) {
+ printf("Left hand: %.*s\nRight hand: %.*s\n",
+ sfmtarg(expr->left?expr->left->srctext:empty),
+ sfmtarg(expr->right?expr->right->srctext:empty));
+ }
+ }
+}
+
+static void dav_debug_ql_field_print(DavQLField *field) {
+ if (field) {
+ printf("Name: %.*s\n", sfmtarg(field->name));
+ if (field->expr) {
+ dav_debug_ql_expr_print(field->expr);
+ } else {
+ printf("No expression.\n");
+ }
+ } else {
+ printf("No field selected.\n");
+ }
+}
+
+static void dav_debug_ql_tree_print(DavQLExpression *expr, int depth) {
+ if (expr) {
+ if (expr->left) {
+ printf("%*c%s\n", depth, ' ', _map_operator(expr->op));
+ dav_debug_ql_tree_print(expr->left, depth+1);
+ dav_debug_ql_tree_print(expr->right, depth+1);
+ } else if (expr->type == DAVQL_UNARY) {
+ printf("%*c%s %.*s\n", depth, ' ', _map_operator(expr->op),
+ sfmtarg(expr->srctext));
+ } else {
+ printf("%*c%.*s\n", depth, ' ', sfmtarg(expr->srctext));
+ }
+ }
+}
+
+#define DQLD_CMD_Q 0
+#define DQLD_CMD_PS 1
+#define DQLD_CMD_PE 2
+#define DQLD_CMD_PF 3
+#define DQLD_CMD_PT 4
+#define DQLD_CMD_F 10
+#define DQLD_CMD_W 11
+#define DQLD_CMD_O 12
+#define DQLD_CMD_L 21
+#define DQLD_CMD_R 22
+#define DQLD_CMD_N 23
+#define DQLD_CMD_P 24
+#define DQLD_CMD_H 100
+
+static int dav_debug_ql_command() {
+ printf("> ");
+
+ char buffer[8];
+ fgets(buffer, 8, stdin);
+ // discard remaining chars
+ if (!strchr(buffer, '\n')) {
+ int chr;
+ while ((chr = fgetc(stdin) != '\n') && chr != EOF);
+ }
+
+ if (!strcmp(buffer, "q\n")) {
+ return DQLD_CMD_Q;
+ } else if (!strcmp(buffer, "ps\n")) {
+ return DQLD_CMD_PS;
+ } else if (!strcmp(buffer, "pe\n")) {
+ return DQLD_CMD_PE;
+ } else if (!strcmp(buffer, "pf\n")) {
+ return DQLD_CMD_PF;
+ } else if (!strcmp(buffer, "pt\n")) {
+ return DQLD_CMD_PT;
+ } else if (!strcmp(buffer, "l\n")) {
+ return DQLD_CMD_L;
+ } else if (!strcmp(buffer, "r\n")) {
+ return DQLD_CMD_R;
+ } else if (!strcmp(buffer, "h\n")) {
+ return DQLD_CMD_H;
+ } else if (!strcmp(buffer, "f\n")) {
+ return DQLD_CMD_F;
+ } else if (!strcmp(buffer, "w\n")) {
+ return DQLD_CMD_W;
+ } else if (!strcmp(buffer, "o\n")) {
+ return DQLD_CMD_O;
+ } else if (!strcmp(buffer, "n\n")) {
+ return DQLD_CMD_N;
+ } else if (!strcmp(buffer, "p\n")) {
+ return DQLD_CMD_P;
+ } else {
+ return -1;
+ }
+}
+
+void dav_debug_statement(DavQLStatement *stmt) {
+ if (!stmt) {
+ fprintf(stderr, "Debug DavQLStatement failed: null pointer");
+ return;
+ }
+
+ printf("Starting DavQL debugger (type 'h' for help)...\n\n");
+ dav_debug_ql_stmt_print(stmt);
+
+ if (stmt->errorcode) {
+ return;
+ }
+
+ DavQLExpression *examineexpr = NULL;
+ UcxList *examineelem = NULL;
+ int examineclause = 0;
+
+ while(1) {
+ int cmd = dav_debug_ql_command();
+ switch (cmd) {
+ case DQLD_CMD_Q: return;
+ case DQLD_CMD_PS: dav_debug_ql_stmt_print(stmt); break;
+ case DQLD_CMD_PE: dav_debug_ql_expr_print(examineexpr); break;
+ case DQLD_CMD_PT: dav_debug_ql_tree_print(examineexpr, 1); break;
+ case DQLD_CMD_PF: dav_debug_ql_fnames_print(stmt); break;
+ case DQLD_CMD_F:
+ examineclause = DQLD_CMD_F;
+ examineelem = stmt->fields;
+ if (stmt->fields) {
+ DavQLField* field = ((DavQLField*)stmt->fields->data);
+ examineexpr = field->expr;
+ dav_debug_ql_field_print(field);
+ } else {
+ examineexpr = NULL;
+ }
+ break;
+ case DQLD_CMD_W:
+ examineclause = 0; examineelem = NULL;
+ examineexpr = stmt->where;
+ dav_debug_ql_expr_print(examineexpr);
+ break;
+ case DQLD_CMD_O:
+ examineclause = DQLD_CMD_O;
+ examineelem = stmt->orderby;
+ examineexpr = stmt->orderby ?
+ ((DavQLOrderCriterion*)stmt->orderby->data)->column : NULL;
+ dav_debug_ql_expr_print(examineexpr);
+ break;
+ case DQLD_CMD_N:
+ case DQLD_CMD_P:
+ if (examineelem) {
+ UcxList *newelem = (cmd == DQLD_CMD_N ?
+ examineelem->next : examineelem->prev);
+ if (newelem) {
+ examineelem = newelem;
+ if (examineclause == DQLD_CMD_O) {
+ examineexpr = ((DavQLOrderCriterion*)
+ examineelem->data)->column;
+ dav_debug_ql_expr_print(examineexpr);
+ } else if (examineclause == DQLD_CMD_F) {
+ DavQLField* field = (DavQLField*)examineelem->data;
+ examineexpr = field->expr;
+ dav_debug_ql_field_print(field);
+ } else {
+ printf("Examining unknown clause type.");
+ }
+ } else {
+ printf("Reached end of list.\n");
+ }
+ } else {
+ printf("Currently not examining an expression list.\n");
+ }
+ break;
+ case DQLD_CMD_L:
+ if (dav_debug_ql_expr_selected(examineexpr)) {
+ if (examineexpr->left) {
+ examineexpr = examineexpr->left;
+ dav_debug_ql_expr_print(examineexpr);
+ } else {
+ printf("There is no left subtree.\n");
+ }
+ }
+ break;
+ case DQLD_CMD_R:
+ if (dav_debug_ql_expr_selected(examineexpr)) {
+ if (examineexpr->right) {
+ examineexpr = examineexpr->right;
+ dav_debug_ql_expr_print(examineexpr);
+ } else {
+ printf("There is no right subtree.\n");
+ }
+ }
+ break;
+ case DQLD_CMD_H:
+ printf(
+ "\nCommands:\n"
+ "ps: print statement information\n"
+ "o: examine order by clause\n"
+ "f: examine field list\n"
+ "pf: print field names\n"
+ "w: examine where clause\n"
+ "n: examine next expression "
+ "(in order by clause or field list)\n"
+ "p: examine previous expression "
+ "(in order by clause or field list)\n"
+ "q: quit\n\n"
+ "\nExpression examination:\n"
+ "pe: print expression information\n"
+ "pt: print full syntax tree of current (sub-)expression\n"
+ "l: enter left subtree\n"
+ "r: enter right subtree\n");
+ break;
+ default: printf("unknown command\n");
+ }
+ }
+}
+
+// ------------------------------------------------------------------------
+// P A R S E R
+// ------------------------------------------------------------------------
+
+#define _error_context "(%.*s[->]%.*s%.*s)"
+#define _error_invalid "invalid statement"
+#define _error_out_of_memory "out of memory"
+#define _error_unexpected_token "unexpected token " _error_context
+#define _error_invalid_token "invalid token " _error_context
+#define _error_missing_path "expected path " _error_context
+#define _error_missing_from "expecting FROM keyword " _error_context
+#define _error_missing_at "expecting AT keyword " _error_context
+#define _error_missing_by "expecting BY keyword " _error_context
+#define _error_missing_as "expecting alias ('as <identifier>') " _error_context
+#define _error_missing_identifier "expecting identifier " _error_context
+#define _error_missing_par "missing closed parenthesis " _error_context
+#define _error_missing_assign "expecting assignment ('=') " _error_context
+#define _error_missing_where "SET statements must have a WHERE clause or " \
+ "explicitly use ANYWHERE " _error_context
+#define _error_invalid_depth "invalid depth " _error_context
+#define _error_missing_expr "missing expression " _error_context
+#define _error_invalid_expr "invalid expression " _error_context
+#define _error_invalid_unary_op "invalid unary operator " _error_context
+#define _error_invalid_logical_op "invalid logical operator " _error_context
+#define _error_invalid_fmtspec "invalid format specifier " _error_context
+#define _error_invalid_string "string expected " _error_context
+#define _error_invalid_order_criterion "invalid order criterion " _error_context
+
+#define token_sstr(token) (((DavQLToken*)(token)->data)->value)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+ DavQLStatement *stmt, UcxList *token) {
+
+ // we try to achieve two things: get as many information as possible
+ // and recover the concrete source string (and not the token strings)
+ sstr_t emptystring = ST("");
+ sstr_t prev = token->prev ? (token->prev->prev ?
+ token_sstr(token->prev->prev) : token_sstr(token->prev))
+ : emptystring;
+ sstr_t tokenstr = token_sstr(token);
+ sstr_t next = token->next ? (token->next->next ?
+ token_sstr(token->next->next) : token_sstr(token->next))
+ : emptystring;
+
+ int lp = prev.length == 0 ? 0 : tokenstr.ptr-prev.ptr;
+ char *pn = tokenstr.ptr + tokenstr.length;
+ int ln = next.ptr+next.length - pn;
+
+ stmt->errorcode = errorcode;
+ stmt->errormessage = ucx_sprintf(errormsg,
+ lp, prev.ptr,
+ sfmtarg(tokenstr),
+ ln, pn).ptr;
+}
+
+#define dqlsec_alloc_failed(ptr, stmt) \
+ if (!(ptr)) do { \
+ (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; \
+ return 0; \
+ } while(0)
+#define dqlsec_malloc(stmt, ptr, type) \
+ dqlsec_alloc_failed(ptr = malloc(sizeof(type)), stmt)
+#define dqlsec_mallocz(stmt, ptr, type) \
+ dqlsec_alloc_failed(ptr = calloc(1, sizeof(type)), stmt)
+
+#define dqlsec_list_append_or_free(stmt, list, data) \
+ do { \
+ UcxList *_dqlsecbak_ = list; \
+ list = ucx_list_append(list, data); \
+ if (!list) { \
+ free(data); \
+ data = NULL; \
+ (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY; \
+ list = _dqlsecbak_; \
+ return 0; \
+ } \
+ } while(0)
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
+
+static _Bool iskeyword(DavQLToken *token) {
+ sstr_t keywords[] ={ST("select"), ST("set"), ST("from"), ST("at"), ST("as"),
+ ST("where"), ST("anywhere"), ST("like"), ST("unlike"), ST("and"),
+ ST("or"), ST("not"), ST("xor"), ST("with"), ST("infinity"),
+ ST("order"), ST("by"), ST("asc"), ST("desc")
+ };
+ for (int i = 0 ; i < sizeof(keywords)/sizeof(sstr_t) ; i++) {
+ if (!sstrcasecmp(token->value, keywords[i])) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static _Bool islongoperator(DavQLToken *token) {
+ sstr_t operators[] = {ST("and"), ST("or"), ST("not"), ST("xor"),
+ ST("like"), ST("unlike")
+ };
+ for (int i = 0 ; i < sizeof(operators)/sizeof(sstr_t) ; i++) {
+ if (!sstrcasecmp(token->value, operators[i])) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static UcxList* dav_parse_add_token(UcxList *tokenlist, DavQLToken *token) {
+
+ // determine token class (order of if-statements is very important!)
+ char firstchar = token->value.ptr[0];
+
+ if (isdigit(firstchar)) {
+ token->tokenclass = DAVQL_TOKEN_NUMBER;
+ // check, if all characters are digits
+ for (size_t i = 1 ; i < token->value.length ; i++) {
+ if (!isdigit(token->value.ptr[i])) {
+ token->tokenclass = DAVQL_TOKEN_INVALID;
+ break;
+ }
+ }
+ } else if (firstchar == '%') {
+ token->tokenclass = DAVQL_TOKEN_FMTSPEC;
+ } else if (token->value.length == 1) {
+ switch (firstchar) {
+ case '(': token->tokenclass = DAVQL_TOKEN_OPENP; break;
+ case ')': token->tokenclass = DAVQL_TOKEN_CLOSEP; break;
+ case ',': token->tokenclass = DAVQL_TOKEN_COMMA; break;
+ default:
+ token->tokenclass = strchr(special_token_symbols, firstchar) ?
+ DAVQL_TOKEN_OPERATOR : DAVQL_TOKEN_IDENTIFIER;
+ }
+ } else if (islongoperator(token)) {
+ token->tokenclass = DAVQL_TOKEN_OPERATOR;
+ } else if (firstchar == '\'') {
+ token->tokenclass = DAVQL_TOKEN_STRING;
+ } else if (firstchar == '`') {
+ token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+ } else if (iskeyword(token)) {
+ token->tokenclass = DAVQL_TOKEN_KEYWORD;
+ } else {
+ token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+ // TODO: check for illegal characters
+ }
+
+ // remove quotes (extreme cool feature)
+ if (token->tokenclass == DAVQL_TOKEN_STRING ||
+ (token->tokenclass == DAVQL_TOKEN_IDENTIFIER && firstchar == '`')) {
+
+ char lastchar = token->value.ptr[token->value.length-1];
+ if (firstchar == lastchar) {
+ token->value.ptr++;
+ token->value.length -= 2;
+ } else {
+ token->tokenclass = DAVQL_TOKEN_INVALID;
+ }
+ }
+
+
+ UcxList *ret = ucx_list_append(tokenlist, token);
+ if (ret) {
+ return ret;
+ } else {
+ ucx_list_free(tokenlist);
+ return NULL;
+ }
+}
+
+static UcxList* dav_parse_tokenize(sstr_t src) {
+#define alloc_token() do {token = malloc(sizeof(DavQLToken));\
+ if(!token) {ucx_list_free(tokens); return NULL;}} while(0)
+#define add_token() do {tokens = dav_parse_add_token(tokens, token); \
+ if(!tokens) {return NULL;}} while(0)
+ UcxList *tokens = NULL;
+
+ DavQLToken *token = NULL;
+ char insequence = '\0';
+ for (size_t i = 0 ; i < src.length ; i++) {
+ // quoted strings / identifiers are a single token
+ if (src.ptr[i] == '\'' || src.ptr[i] == '`') {
+ if (src.ptr[i] == insequence) {
+ // lookahead for escaped string quotes
+ if (src.ptr[i] == '\'' && i+2 < src.length &&
+ src.ptr[i+1] == src.ptr[i] && src.ptr[i+2] == src.ptr[i]) {
+ token->value.length += 3;
+ i += 2;
+ } else {
+ // add quoted token to list
+ token->value.length++;
+ add_token();
+ token = NULL;
+ insequence = '\0';
+ }
+ } else if (insequence == '\0') {
+ insequence = src.ptr[i];
+ // always create new token for quoted strings
+ if (token) {
+ add_token();
+ }
+ alloc_token();
+ token->value.ptr = src.ptr + i;
+ token->value.length = 1;
+ } else {
+ // add other kind of quotes to token
+ token->value.length++;
+ }
+ } else if (insequence) {
+ token->value.length++;
+ } else if (isspace(src.ptr[i])) {
+ // add token before spaces to list (if any)
+ if (token) {
+ add_token();
+ token = NULL;
+ }
+ } else if (strchr(special_token_symbols, src.ptr[i])) {
+ // add token before special symbol to list (if any)
+ if (token) {
+ add_token();
+ token = NULL;
+ }
+ // add special symbol as single token to list
+ alloc_token();
+ token->value.ptr = src.ptr + i;
+ token->value.length = 1;
+ add_token();
+ // set tokenizer ready to read more tokens
+ token = NULL;
+ } else {
+ // if this is a new token, create memory for it
+ if (!token) {
+ alloc_token();
+ token->value.ptr = src.ptr + i;
+ token->value.length = 0;
+ }
+ // extend token length when reading more bytes
+ token->value.length++;
+ }
+ }
+
+ if (token) {
+ add_token();
+ }
+
+ alloc_token();
+ token->tokenclass = DAVQL_TOKEN_END;
+ token->value = S("");
+ UcxList *ret = ucx_list_append(tokens, token);
+ if (ret) {
+ return ret;
+ } else {
+ ucx_list_free(tokens);
+ return NULL;
+ }
+#undef alloc_token
+#undef add_token
+}
+
+static void dav_free_expression(DavQLExpression *expr) {
+ if (expr) {
+ if (expr->left) {
+ dav_free_expression(expr->left);
+ }
+ if (expr->right) {
+ dav_free_expression(expr->right);
+ }
+ free(expr);
+ }
+}
+
+static void dav_free_field(DavQLField *field) {
+ dav_free_expression(field->expr);
+ free(field);
+}
+
+static void dav_free_order_criterion(DavQLOrderCriterion *crit) {
+ if (crit->column) { // do it null-safe though column is expected to be set
+ dav_free_expression(crit->column);
+ }
+ free(crit);
+}
+
+#define token_is(token, expectedclass) (token && \
+ (((DavQLToken*)(token)->data)->tokenclass == expectedclass))
+
+#define tokenvalue_is(token, expectedvalue) (token && \
+ !sstrcasecmp(((DavQLToken*)(token)->data)->value, S(expectedvalue)))
+
+typedef int(*exprparser_f)(DavQLStatement*,UcxList*,DavQLExpression*);
+
+static int dav_parse_binary_expr(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr, exprparser_f parseL, char* opc, int* opv,
+ exprparser_f parseR) {
+
+ if (!token) {
+ return 0;
+ }
+
+ int total_consumed = 0, consumed;
+
+ // save temporarily on stack (copy to heap later on)
+ DavQLExpression left, right;
+
+ // RULE: LEFT, [Operator, RIGHT]
+ memset(&left, 0, sizeof(DavQLExpression));
+ consumed = parseL(stmt, token, &left);
+ if (!consumed || stmt->errorcode) {
+ return 0;
+ }
+ total_consumed += consumed;
+ token = ucx_list_get(token, consumed);
+
+ char *op;
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) &&
+ (op = strchr(opc, token_sstr(token).ptr[0]))) {
+ expr->op = opv[op-opc];
+ expr->type = DAVQL_BINARY;
+ total_consumed++;
+ token = token->next;
+ memset(&right, 0, sizeof(DavQLExpression));
+ consumed = parseR(stmt, token, &right);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ total_consumed += consumed;
+ }
+
+ if (expr->op == DAVQL_NOOP) {
+ memcpy(expr, &left, sizeof(DavQLExpression));
+ } else {
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &left, sizeof(DavQLExpression));
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &right, sizeof(DavQLExpression));
+
+ expr->srctext.ptr = expr->left->srctext.ptr;
+ expr->srctext.length =
+ expr->right->srctext.ptr -
+ expr->left->srctext.ptr + expr->right->srctext.length;
+ }
+
+ return total_consumed;
+}
+
+static void dav_add_fmt_args(DavQLStatement *stmt, sstr_t str) {
+ int placeholder = 0;
+ for (size_t i=0;i<str.length;i++) {
+ char c = str.ptr[i];
+ if (placeholder) {
+ if (c != '%') {
+ stmt->args = ucx_list_append(
+ stmt->args,
+ (void*)(intptr_t)c);
+ }
+ placeholder = 0;
+ } else if (c == '%') {
+ placeholder = 1;
+ }
+ }
+}
+
+static int dav_parse_literal(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr) {
+
+ expr->srctext = token_sstr(token);
+ if (token_is(token, DAVQL_TOKEN_NUMBER)) {
+ expr->type = DAVQL_NUMBER;
+ } else if (token_is(token, DAVQL_TOKEN_STRING)) {
+ expr->type = DAVQL_STRING;
+ // check for format specifiers and add args
+ dav_add_fmt_args(stmt, expr->srctext);
+ } else if (token_is(token, DAVQL_TOKEN_TIMESTAMP)) {
+ expr->type = DAVQL_TIMESTAMP;
+ } else if (token_is(token, DAVQL_TOKEN_FMTSPEC)
+ && expr->srctext.length == 2) {
+ switch (expr->srctext.ptr[1]) {
+ case 'd': expr->type = DAVQL_NUMBER; break;
+ case 's': expr->type = DAVQL_STRING; break;
+ case 't': expr->type = DAVQL_TIMESTAMP; break;
+ default:
+ dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC,
+ _error_invalid_fmtspec, stmt, token);
+ return 0;
+ }
+ // add fmtspec type to query arg list
+ stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)expr->srctext.ptr[1]);
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+// forward declaration
+static int dav_parse_expression(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr);
+
+static int dav_parse_arglist(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr) {
+
+ expr->srctext.ptr = token_sstr(token).ptr;
+ expr->srctext.length = 0;
+ expr->left = expr->right = NULL; // in case we fail, we want them to be sane
+
+ int total_consumed = 0;
+
+ // RULE: Expression, {",", Expression};
+ DavQLExpression *arglist = expr;
+ DavQLExpression arg;
+ char *lastchar = expr->srctext.ptr;
+ int consumed;
+ do {
+ memset(&arg, 0, sizeof(DavQLExpression));
+ consumed = dav_parse_expression(stmt, token, &arg);
+ if (consumed) {
+ lastchar = arg.srctext.ptr + arg.srctext.length;
+ total_consumed += consumed;
+ token = ucx_list_get(token, consumed);
+ // look ahead for a comma
+ if (token_is(token, DAVQL_TOKEN_COMMA)) {
+ total_consumed++;
+ token = token->next;
+ /* we have more arguments, so put the current argument to the
+ * left subtree and create a new node to the right
+ */
+ dqlsec_malloc(stmt, arglist->left, DavQLExpression);
+ memcpy(arglist->left, &arg, sizeof(DavQLExpression));
+ arglist->srctext.ptr = arg.srctext.ptr;
+ arglist->op = DAVQL_ARGLIST;
+ arglist->type = DAVQL_FUNCCALL;
+ dqlsec_mallocz(stmt, arglist->right, DavQLExpression);
+ arglist = arglist->right;
+ } else {
+ // this was the last argument, so write it to the current node
+ memcpy(arglist, &arg, sizeof(DavQLExpression));
+ consumed = 0;
+ }
+ }
+ } while (consumed && !stmt->errorcode);
+
+ // recover source text
+ arglist = expr;
+ while (arglist && arglist->type == DAVQL_FUNCCALL) {
+ arglist->srctext.length = lastchar - arglist->srctext.ptr;
+ arglist = arglist->right;
+ }
+
+ return total_consumed;
+}
+
+static int dav_parse_funccall(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr) {
+
+ // RULE: Identifier, "(", ArgumentList, ")";
+ if (token_is(token, DAVQL_TOKEN_IDENTIFIER) &&
+ token_is(token->next, DAVQL_TOKEN_OPENP)) {
+
+ expr->type = DAVQL_FUNCCALL;
+ expr->op = DAVQL_CALL;
+
+ dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+ expr->left->type = DAVQL_IDENTIFIER;
+ expr->left->srctext = token_sstr(token);
+ expr->right = NULL;
+
+ token = token->next->next;
+
+ DavQLExpression arg;
+ int argtokens = dav_parse_arglist(stmt, token, &arg);
+ if (stmt->errorcode) {
+ // if an error occurred while parsing the arglist, return now
+ return 2;
+ }
+ if (argtokens) {
+ token = ucx_list_get(token, argtokens);
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &arg, sizeof(DavQLExpression));
+ } else {
+ // arg list may be empty
+ expr->right = NULL;
+ }
+
+ if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+ return 3 + argtokens;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+ stmt, token);
+ return 2; // it MUST be a function call, but it is invalid
+ }
+ } else {
+ return 0;
+ }
+}
+
+static int dav_parse_unary_expr(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr) {
+
+ UcxList *firsttoken = token; // save for srctext recovery
+
+ DavQLExpression* atom = expr;
+ int total_consumed = 0;
+
+ // optional unary operator
+ if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+ char *op = strchr("+-~", token_sstr(token).ptr[0]);
+ if (op) {
+ expr->type = DAVQL_UNARY;
+ switch (*op) {
+ case '+': expr->op = DAVQL_ADD; break;
+ case '-': expr->op = DAVQL_SUB; break;
+ case '~': expr->op = DAVQL_NEG; break;
+ }
+ dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+ atom = expr->left;
+ total_consumed++;
+ token = token->next;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_INVALID_UNARY_OP,
+ _error_invalid_unary_op, stmt, token);
+ return 0;
+ }
+ }
+
+ // RULE: (ParExpression | AtomicExpression)
+ if (token_is(token, DAVQL_TOKEN_OPENP)) {
+ token = token->next; total_consumed++;
+ // RULE: "(", Expression, ")"
+ int consumed = dav_parse_expression(stmt, token, atom);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+ _error_invalid_expr, stmt, token);
+ return 0;
+ }
+ token = ucx_list_get(token, consumed);
+ total_consumed += consumed;
+ if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+ token = token->next; total_consumed++;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_PAR,
+ _error_missing_par, stmt, token);
+ return 0;
+ }
+ } else {
+ // RULE: FunctionCall
+ int consumed = dav_parse_funccall(stmt, token, atom);
+ if (consumed) {
+ total_consumed += consumed;
+ } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+ // RULE: Identifier
+ total_consumed++;
+ atom->type = DAVQL_IDENTIFIER;
+ atom->srctext = token_sstr(token);
+ } else {
+ // RULE: Literal
+ total_consumed += dav_parse_literal(stmt, token, atom);
+ }
+ }
+
+ // recover source text
+ expr->srctext.ptr = token_sstr(firsttoken).ptr;
+ if (total_consumed > 0) {
+ sstr_t lasttoken =
+ token_sstr(ucx_list_get(firsttoken, total_consumed-1));
+ expr->srctext.length =
+ lasttoken.ptr - expr->srctext.ptr + lasttoken.length;
+ } else {
+ // the expression should not be used anyway, but we want to be safe
+ expr->srctext.length = 0;
+ }
+
+
+ return total_consumed;
+}
+
+static int dav_parse_bitexpr(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr) {
+
+ return dav_parse_binary_expr(stmt, token, expr,
+ dav_parse_unary_expr,
+ "&|^", (int[]){DAVQL_AND, DAVQL_OR, DAVQL_XOR},
+ dav_parse_bitexpr);
+}
+
+static int dav_parse_multexpr(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr) {
+
+ return dav_parse_binary_expr(stmt, token, expr,
+ dav_parse_bitexpr,
+ "*/", (int[]){DAVQL_MUL, DAVQL_DIV},
+ dav_parse_multexpr);
+}
+
+static int dav_parse_expression(DavQLStatement* stmt, UcxList* token,
+ DavQLExpression* expr) {
+
+ return dav_parse_binary_expr(stmt, token, expr,
+ dav_parse_multexpr,
+ "+-", (int[]){DAVQL_ADD, DAVQL_SUB},
+ dav_parse_expression);
+}
+
+static int dav_parse_named_field(DavQLStatement *stmt, UcxList *token,
+ DavQLField *field) {
+ int total_consumed = 0, consumed;
+
+ // RULE: Expression, " as ", Identifier;
+ DavQLExpression *expr;
+ dqlsec_mallocz(stmt, expr, DavQLExpression);
+ consumed = dav_parse_expression(stmt, token, expr);
+ if (stmt->errorcode) {
+ dav_free_expression(expr);
+ return 0;
+ }
+ if (expr->type == DAVQL_UNDEFINED_TYPE) {
+ dav_free_expression(expr);
+ dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+ _error_invalid_expr, stmt, token);
+ return 0;
+ }
+
+ token = ucx_list_get(token, consumed);
+ total_consumed += consumed;
+
+ if (token_is(token, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(token, "as")) {
+ token = token->next; total_consumed++;
+ } else {
+ dav_free_expression(expr);
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_as, stmt, token);
+ return 0;
+ }
+
+ if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+ field->name = token_sstr(token);
+ field->expr = expr;
+ return total_consumed + 1;
+ } else {
+ dav_free_expression(expr);
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_identifier, stmt, token);
+ return 0;
+ }
+}
+
+static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token) {
+
+ // RULE: "-"
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) {
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ dqlsec_list_append_or_free(stmt, stmt->fields, field);
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ field->expr->type = DAVQL_IDENTIFIER;
+ field->expr->srctext = field->name = token_sstr(token);
+ return 1;
+ }
+
+ // RULE: "*", {",", NamedExpression}
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "*")) {
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ dqlsec_list_append_or_free(stmt, stmt->fields, field);
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ field->expr->type = DAVQL_IDENTIFIER;
+ field->expr->srctext = field->name = token_sstr(token);
+
+ int total_consumed = 0;
+ int consumed = 1;
+
+ do {
+ token = ucx_list_get(token, consumed);
+ total_consumed += consumed;
+
+ if (token_is(token, DAVQL_TOKEN_COMMA)) {
+ total_consumed++; token = token->next;
+ DavQLField localfield;
+ consumed = dav_parse_named_field(stmt, token, &localfield);
+ if (!stmt->errorcode && consumed) {
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ memcpy(field, &localfield, sizeof(DavQLField));
+ dqlsec_list_append_or_free(stmt, stmt->fields, field);
+ }
+ } else {
+ consumed = 0;
+ }
+ } while (consumed > 0);
+
+ return total_consumed;
+ }
+
+ // RULE: FieldExpression, {",", FieldExpression}
+ {
+ int total_consumed = 0, consumed;
+ do {
+ // RULE: NamedField | Identifier
+ DavQLField localfield;
+ consumed = dav_parse_named_field(stmt, token, &localfield);
+ if (consumed) {
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ memcpy(field, &localfield, sizeof(DavQLField));
+ dqlsec_list_append_or_free(stmt, stmt->fields, field);
+ token = ucx_list_get(token, consumed);
+ total_consumed += consumed;
+ } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)
+ // look ahead, if the field is JUST the identifier
+ && (token_is(token->next, DAVQL_TOKEN_COMMA) ||
+ tokenvalue_is(token->next, "from"))) {
+
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ field->expr->type = DAVQL_IDENTIFIER;
+ field->expr->srctext = field->name = token_sstr(token);
+ dqlsec_list_append_or_free(stmt, stmt->fields, field);
+
+ consumed = 1;
+ total_consumed++;
+ token = token->next;
+
+ // we found a valid solution, so erase any errors
+ stmt->errorcode = 0;
+ if (stmt->errormessage) {
+ free(stmt->errormessage);
+ stmt->errormessage = NULL;
+ }
+ } else {
+ // dav_parse_named_field has already thrown a good error
+ consumed = 0;
+ }
+
+ // field has been parsed, now try to get a comma
+ if (consumed) {
+ consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+ if (consumed) {
+ token = token->next;
+ total_consumed++;
+ }
+ }
+ } while (consumed);
+
+ return total_consumed;
+ }
+}
+
+// forward declaration
+static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token,
+ DavQLExpression *expr);
+
+static int dav_parse_bool_prim(DavQLStatement *stmt, UcxList *token,
+ DavQLExpression *expr) {
+
+ expr->type = DAVQL_LOGICAL;
+ expr->srctext = token_sstr(token);
+
+ int total_consumed = 0;
+
+ DavQLExpression bexpr;
+ memset(&bexpr, 0, sizeof(DavQLExpression));
+ total_consumed = dav_parse_expression(stmt, token, &bexpr);
+ if (!total_consumed || stmt->errorcode) {
+ return 0;
+ }
+ token = ucx_list_get(token, total_consumed);
+
+ UcxList* optok = token;
+ // RULE: Expression, (" like " | " unlike "), String
+ if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (tokenvalue_is(optok,
+ "like") || tokenvalue_is(optok, "unlike"))) {
+
+ total_consumed++;
+ token = token->next;
+ if (token_is(token, DAVQL_TOKEN_STRING)) {
+ expr->op = tokenvalue_is(optok, "like") ?
+ DAVQL_LIKE : DAVQL_UNLIKE;
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+ dqlsec_mallocz(stmt, expr->right, DavQLExpression);
+ expr->right->type = DAVQL_STRING;
+ expr->right->srctext = token_sstr(token);
+ expr->srctext.length = expr->right->srctext.ptr -
+ expr->srctext.ptr + expr->right->srctext.length;
+
+ // fmt args
+ dav_add_fmt_args(stmt, expr->right->srctext);
+
+ return total_consumed + 1;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_INVALID_STRING,
+ _error_invalid_string, stmt, token);
+ return 0;
+ }
+ }
+ // RULE: Expression, Comparison, Expression
+ else if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (
+ tokenvalue_is(optok, "=") || tokenvalue_is(optok, "!") ||
+ tokenvalue_is(optok, "<") || tokenvalue_is(optok, ">"))) {
+
+ total_consumed++;
+ token = token->next;
+
+ if (tokenvalue_is(optok, "=")) {
+ expr->op = DAVQL_EQ;
+ } else {
+ if (tokenvalue_is(token, "=")) {
+ if (tokenvalue_is(optok, "!")) {
+ expr->op = DAVQL_NEQ;
+ } else if (tokenvalue_is(optok, "<")) {
+ expr->op = DAVQL_LE;
+ } else if (tokenvalue_is(optok, ">")) {
+ expr->op = DAVQL_GE;
+ }
+ total_consumed++;
+ token = token->next;
+ } else {
+ if (tokenvalue_is(optok, "<")) {
+ expr->op = DAVQL_LT;
+ } else if (tokenvalue_is(optok, ">")) {
+ expr->op = DAVQL_GT;
+ }
+ }
+ }
+
+ DavQLExpression rexpr;
+ memset(&rexpr, 0, sizeof(DavQLExpression));
+ int consumed = dav_parse_expression(stmt, token, &rexpr);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(
+ DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+ stmt, token);
+ return 0;
+ }
+
+ total_consumed += consumed;
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &rexpr, sizeof(DavQLExpression));
+
+ expr->srctext.length = expr->right->srctext.ptr -
+ expr->srctext.ptr + expr->right->srctext.length;
+
+ return total_consumed;
+ }
+ // RULE: FunctionCall | Identifier;
+ else if (bexpr.type == DAVQL_FUNCCALL || bexpr.type == DAVQL_IDENTIFIER) {
+ memcpy(expr, &bexpr, sizeof(DavQLExpression));
+
+ return total_consumed;
+ } else {
+ return 0;
+ }
+}
+
+static int dav_parse_bool_expr(DavQLStatement *stmt, UcxList *token,
+ DavQLExpression *expr) {
+
+ // RULE: "not ", LogicalExpression
+ if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "not")) {
+ expr->type = DAVQL_LOGICAL;
+ expr->op = DAVQL_NOT;
+ dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+ expr->srctext = token_sstr(token);
+
+ token = token->next;
+ int consumed = dav_parse_bool_expr(stmt, token, expr->left);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (consumed) {
+ sstr_t lasttok = token_sstr(ucx_list_get(token, consumed-1));
+ expr->srctext.length =
+ lasttok.ptr - expr->srctext.ptr + lasttok.length;
+ return consumed + 1;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ }
+ // RULE: "(", LogicalExpression, ")"
+ else if (token_is(token, DAVQL_TOKEN_OPENP)) {
+ int consumed = dav_parse_logical_expr(stmt, token->next, expr);
+ if (consumed) {
+ token = ucx_list_get(token->next, consumed);
+
+ if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+ token = token->next;
+ return consumed + 2;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+ stmt, token);
+ return 0;
+ }
+ } else {
+ // don't handle errors here, we can also try a boolean primary
+ stmt->errorcode = 0;
+ if (stmt->errormessage) {
+ free(stmt->errormessage);
+ }
+ }
+ }
+
+ // RULE: BooleanPrimary
+ return dav_parse_bool_prim(stmt, token, expr);
+}
+
+static int dav_parse_logical_expr(DavQLStatement *stmt, UcxList *token,
+ DavQLExpression *expr) {
+
+ UcxList *firsttoken = token;
+ int total_consumed = 0;
+
+ // RULE: BooleanLiteral, [LogicalOperator, LogicalExpression];
+ DavQLExpression left, right;
+ memset(&left, 0, sizeof(DavQLExpression));
+ int consumed = dav_parse_bool_expr(stmt, token, &left);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ total_consumed += consumed;
+ token = ucx_list_get(token, consumed);
+
+ if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+ expr->type = DAVQL_LOGICAL;
+
+ davqloperator_t op = DAVQL_NOOP;
+ if (tokenvalue_is(token, "and")) {
+ op = DAVQL_LAND;
+ } else if (tokenvalue_is(token, "or")) {
+ op = DAVQL_LOR;
+ } else if (tokenvalue_is(token, "xor")) {
+ op = DAVQL_LXOR;
+ }
+
+ if (op == DAVQL_NOOP) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_LOGICAL_OP,
+ _error_invalid_logical_op, stmt, token);
+ return 0;
+ } else {
+ expr->op = op;
+ total_consumed++;
+ token = token->next;
+
+ memset(&right, 0, sizeof(DavQLExpression));
+ consumed = dav_parse_logical_expr(stmt, token, &right);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+ _error_missing_expr, stmt, token);
+ return 0;
+ }
+ total_consumed += consumed;
+ token = ucx_list_get(token, consumed);
+
+ dqlsec_malloc(stmt, expr->left, DavQLExpression);
+ memcpy(expr->left, &left, sizeof(DavQLExpression));
+ dqlsec_malloc(stmt, expr->right, DavQLExpression);
+ memcpy(expr->right, &right, sizeof(DavQLExpression));
+ }
+ } else {
+ memcpy(expr, &left, sizeof(DavQLExpression));
+ }
+
+ // set type and recover source text
+ if (total_consumed > 0) {
+ expr->srctext.ptr = token_sstr(firsttoken).ptr;
+ sstr_t lasttok = token_sstr(ucx_list_get(firsttoken, total_consumed-1));
+ expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length;
+ }
+
+ return total_consumed;
+}
+
+static int dav_parse_where_clause(DavQLStatement *stmt, UcxList *token) {
+ dqlsec_mallocz(stmt, stmt->where, DavQLExpression);
+
+ return dav_parse_logical_expr(stmt, token, stmt->where);
+}
+
+static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token) {
+
+ int total_consumed = 0;
+
+ // RULE: "depth", "=", (Number | "infinity")
+ if (tokenvalue_is(token, "depth")) {
+ token = token->next; total_consumed++;
+ if (tokenvalue_is(token, "=")) {
+ token = token->next; total_consumed++;
+ if (tokenvalue_is(token, "infinity")) {
+ stmt->depth = DAV_DEPTH_INFINITY;
+ token = token->next; total_consumed++;
+ } else {
+ DavQLExpression *depthexpr;
+ dqlsec_mallocz(stmt, depthexpr, DavQLExpression);
+
+ int consumed = dav_parse_expression(stmt, token, depthexpr);
+
+ if (consumed) {
+ if (depthexpr->type == DAVQL_NUMBER) {
+ if (depthexpr->srctext.ptr[0] == '%') {
+ stmt->depth = DAV_DEPTH_PLACEHOLDER;
+ } else {
+ sstr_t depthstr = depthexpr->srctext;
+ char *conv = malloc(depthstr.length+1);
+ if (!conv) {
+ dav_free_expression(depthexpr);
+ stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+ return 0;
+ }
+ char *chk;
+ memcpy(conv, depthstr.ptr, depthstr.length);
+ conv[depthstr.length] = '\0';
+ stmt->depth = strtol(conv, &chk, 10);
+ if (*chk || stmt->depth < -1) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+ _error_invalid_depth, stmt, token);
+ }
+ free(conv);
+ }
+ total_consumed += consumed;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+ _error_invalid_depth, stmt, token);
+ }
+ }
+
+ dav_free_expression(depthexpr);
+ }
+ }
+ }
+
+ return total_consumed;
+}
+
+static int dav_parse_order_crit(DavQLStatement *stmt, UcxList *token,
+ DavQLOrderCriterion *crit) {
+
+ // RULE: (Identifier | Number), [" asc"|" desc"];
+ DavQLExpression expr;
+ memset(&expr, 0, sizeof(DavQLExpression));
+ int consumed = dav_parse_expression(stmt, token, &expr);
+ if (stmt->errorcode || !consumed) {
+ return 0;
+ }
+
+ if (expr.type != DAVQL_IDENTIFIER && expr.type != DAVQL_NUMBER) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_ORDER_CRITERION,
+ _error_invalid_order_criterion, stmt, token);
+ return 0;
+ }
+
+ dqlsec_malloc(stmt, crit->column, DavQLExpression);
+ memcpy(crit->column, &expr, sizeof(DavQLExpression));
+
+ token = ucx_list_get(token, consumed);
+ if (token_is(token, DAVQL_TOKEN_KEYWORD) && (
+ tokenvalue_is(token, "asc") || tokenvalue_is(token, "desc"))) {
+
+ crit->descending = tokenvalue_is(token, "desc");
+
+ return consumed+1;
+ } else {
+ crit->descending = 0;
+ return consumed;
+ }
+}
+
+static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token) {
+
+ int total_consumed = 0, consumed;
+
+ DavQLOrderCriterion crit;
+
+ // RULE: OrderByCriterion, {",", OrderByCriterion};
+ do {
+ consumed = dav_parse_order_crit(stmt, token, &crit);
+ if (stmt->errorcode) {
+ return 0;
+ }
+ if (!consumed) {
+ dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+ stmt, token);
+ return 0;
+ }
+ token = ucx_list_get(token, consumed);
+ total_consumed += consumed;
+
+ DavQLOrderCriterion *criterion;
+ dqlsec_malloc(stmt, criterion, DavQLOrderCriterion);
+ memcpy(criterion, &crit, sizeof(DavQLOrderCriterion));
+ dqlsec_list_append_or_free(stmt, stmt->orderby, criterion);
+
+ if (token_is(token, DAVQL_TOKEN_COMMA)) {
+ total_consumed++;
+ token = token->next;
+ } else {
+ consumed = 0;
+ }
+ } while (consumed);
+
+ return total_consumed;
+}
+
+
+static int dav_parse_assignments(DavQLStatement *stmt, UcxList *token) {
+
+ // RULE: Assignment, {",", Assignment}
+ int total_consumed = 0, consumed;
+ do {
+ // RULE: Identifier, "=", Expression
+ if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+
+ // Identifier
+ DavQLField *field;
+ dqlsec_malloc(stmt, field, DavQLField);
+ field->name = token_sstr(token);
+ total_consumed++;
+ token = token->next;
+
+ // "="
+ if (!token_is(token, DAVQL_TOKEN_OPERATOR)
+ || !tokenvalue_is(token, "=")) {
+ dav_free_field(field);
+
+ dav_error_in_context(DAVQL_ERROR_MISSING_ASSIGN,
+ _error_missing_assign, stmt, token);
+ return total_consumed;
+ }
+ total_consumed++;
+ token = token->next;
+
+ // Expression
+ dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+ consumed = dav_parse_expression(stmt, token, field->expr);
+ if (stmt->errorcode) {
+ dav_free_field(field);
+ return total_consumed;
+ }
+ token = ucx_list_get(token, consumed);
+ total_consumed += consumed;
+
+ // Add assignment to list and check if there's another one
+ dqlsec_list_append_or_free(stmt, stmt->fields, field);
+ consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+ if (consumed) {
+ token = token->next;
+ total_consumed++;
+ }
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_identifier, stmt, token);
+ return total_consumed;
+ }
+ } while (consumed);
+
+ return total_consumed;
+}
+
+static int dav_parse_path(DavQLStatement *stmt, UcxList *tokens) {
+ if (token_is(tokens, DAVQL_TOKEN_STRING)) {
+ stmt->path = token_sstr(tokens);
+ tokens = tokens->next;
+ return 1;
+ } else if (token_is(tokens, DAVQL_TOKEN_OPERATOR)
+ && tokenvalue_is(tokens, "/")) {
+ stmt->path = token_sstr(tokens);
+ tokens = tokens->next;
+ int consumed = 1;
+ while (!token_is(tokens, DAVQL_TOKEN_KEYWORD) &&
+ !token_is(tokens, DAVQL_TOKEN_END)) {
+ sstr_t toksstr = token_sstr(tokens);
+ stmt->path.length = toksstr.ptr-stmt->path.ptr+toksstr.length;
+ tokens = tokens->next;
+ consumed++;
+ }
+ return consumed;
+ } else if (token_is(tokens, DAVQL_TOKEN_FMTSPEC) &&
+ tokenvalue_is(tokens, "%s")) {
+ stmt->path = token_sstr(tokens);
+ tokens = tokens->next;
+ stmt->args = ucx_list_append(stmt->args, (void*)(intptr_t)'s');
+ return 1;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_path, stmt, tokens);
+ return 0;
+ }
+}
+
+/**
+ * Parser of a select statement.
+ * @param stmt the statement object that shall contain the syntax tree
+ * @param tokens the token list
+ */
+static void dav_parse_select_statement(DavQLStatement *stmt, UcxList *tokens) {
+ stmt->type = DAVQL_SELECT;
+
+ // Consume field list
+ tokens = ucx_list_get(tokens, dav_parse_fieldlist(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume FROM keyword
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "from")) {
+ tokens = tokens->next;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_from, stmt, tokens);
+ return;
+ }
+
+ // Consume path
+ tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+ //dav_add_fmt_args(stmt, stmt->path); // add possible path args
+
+ // Consume with clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "with")) {
+ tokens = tokens->next;
+ tokens = ucx_list_get(tokens,
+ dav_parse_with_clause(stmt, tokens));
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume where clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "where")) {
+ tokens = tokens->next;
+ tokens = ucx_list_get(tokens,
+ dav_parse_where_clause(stmt, tokens));
+ } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "anywhere")) {
+ // useless, but the user may want to explicitly express his intent
+ tokens = tokens->next;
+ stmt->where = NULL;
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume order by clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "order")) {
+ tokens = tokens->next;
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "by")) {
+ tokens = tokens->next;
+ tokens = ucx_list_get(tokens,
+ dav_parse_orderby_clause(stmt, tokens));
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_by, stmt, tokens);
+ return;
+ }
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+
+ if (tokens) {
+ if (token_is(tokens, DAVQL_TOKEN_INVALID)) {
+ dav_error_in_context(DAVQL_ERROR_INVALID_TOKEN,
+ _error_invalid_token, stmt, tokens);
+ } else if (!token_is(tokens, DAVQL_TOKEN_END)) {
+ dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+ _error_unexpected_token, stmt, tokens);
+ }
+ }
+}
+
+static void dav_parse_set_statement(DavQLStatement *stmt, UcxList *tokens) {
+ stmt->type = DAVQL_SET;
+
+ // Consume assignments
+ tokens = ucx_list_get(tokens, dav_parse_assignments(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume AT keyword
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "at")) {
+ tokens = tokens->next;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_at, stmt, tokens);
+ return;
+ }
+
+ // Consume path
+ tokens = ucx_list_get(tokens, dav_parse_path(stmt, tokens));
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume with clause (if any)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "with")) {
+ tokens = tokens->next;
+ tokens = ucx_list_get(tokens,
+ dav_parse_with_clause(stmt, tokens));
+ }
+ if (stmt->errorcode) {
+ return;
+ }
+
+ // Consume mandatory where clause (or anywhere keyword)
+ if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "where")) {
+ tokens = tokens->next;
+ tokens = ucx_list_get(tokens,
+ dav_parse_where_clause(stmt, tokens));
+ } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+ && tokenvalue_is(tokens, "anywhere")) {
+ // no-op, but we want the user to be explicit about this
+ tokens = tokens->next;
+ stmt->where = NULL;
+ } else {
+ dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+ _error_missing_where, stmt, tokens);
+ return;
+ }
+}
+
+DavQLStatement* dav_parse_statement(sstr_t srctext) {
+ DavQLStatement *stmt = calloc(1, sizeof(DavQLStatement));
+
+ // if we can't even get enough memory for the statement object or an error
+ // message, we can simply die without returning anything
+ if (!stmt) {
+ return NULL;
+ }
+ char *oommsg = strdup(_error_out_of_memory);
+ if (!oommsg) {
+ free(stmt);
+ return NULL;
+ }
+
+ // default values
+ stmt->type = -1;
+ stmt->depth = 1;
+
+ // save trimmed source text
+ stmt->srctext = sstrtrim(srctext);
+
+ if (stmt->srctext.length) {
+ // tokenization
+ UcxList* tokens = dav_parse_tokenize(stmt->srctext);
+
+ if (tokens) {
+ // use first token to determine query type
+
+ if (tokenvalue_is(tokens, "select")) {
+ dav_parse_select_statement(stmt, tokens->next);
+ } else if (tokenvalue_is(tokens, "set")) {
+ dav_parse_set_statement(stmt, tokens->next);
+ } else {
+ stmt->type = DAVQL_ERROR;
+ stmt->errorcode = DAVQL_ERROR_INVALID;
+ stmt->errormessage = strdup(_error_invalid);
+ }
+
+ // free token data
+ UCX_FOREACH(token, tokens) {
+ free(token->data);
+ }
+ ucx_list_free(tokens);
+ } else {
+ stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ stmt->type = DAVQL_ERROR;
+ stmt->errorcode = DAVQL_ERROR_INVALID;
+ stmt->errormessage = strdup(_error_invalid);
+ }
+
+ if (stmt->errorcode == DAVQL_ERROR_OUT_OF_MEMORY) {
+ stmt->type = DAVQL_ERROR;
+ stmt->errormessage = oommsg;
+ } else {
+ free(oommsg);
+ }
+
+ return stmt;
+}
+
+void dav_free_statement(DavQLStatement *stmt) {
+ UCX_FOREACH(expr, stmt->fields) {
+ dav_free_field(expr->data);
+ }
+ ucx_list_free(stmt->fields);
+
+ if (stmt->where) {
+ dav_free_expression(stmt->where);
+ }
+ if (stmt->errormessage) {
+ free(stmt->errormessage);
+ }
+ UCX_FOREACH(crit, stmt->orderby) {
+ dav_free_order_criterion(crit->data);
+ }
+ ucx_list_free(stmt->orderby);
+ ucx_list_free(stmt->args);
+ free(stmt);
+}