# HG changeset patch # User Olaf Wintermann # Date 1430557228 -7200 # Node ID 95a215337b53c9ec81138065d4020c2332296e3f # Parent f4127c4d1018f82198cc5a60489109e4e8bb4aef# Parent 57923809797311c00857128afdedc2acdf97c19b merge diff -r f4127c4d1018 -r 95a215337b53 libidav/davqlparser.c --- a/libidav/davqlparser.c Sat May 02 10:59:02 2015 +0200 +++ b/libidav/davqlparser.c Sat May 02 11:00:28 2015 +0200 @@ -49,7 +49,10 @@ static const char* _map_exprtype(davqlexprtype_t type) { switch(type) { - case DAVQL_LITERAL: return "LITERAL"; + case DAVQL_UNDEFINED_TYP: 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"; @@ -90,13 +93,12 @@ // Basic information size_t fieldcount = ucx_list_size(stmt->fields); int specialfield = 0; - UCX_FOREACH(elm, stmt->fields) { - DavQLExpression* expr = (DavQLExpression*)elm->data; - if (expr->type == DAVQL_IDENTIFIER && expr->srctext.length == 1) { - if (expr->srctext.ptr[0] == '*') { - specialfield = 1; - } else if (expr->srctext.ptr[0] == '-') { - specialfield = 2; + 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; } } } @@ -111,18 +113,30 @@ _map_specialfield(specialfield), sfmtarg(stmt->path), stmt->where ? "yes" : "no"); - if (stmt->type == DAVQL_SET) { - printf("Value list size matches: %s", - ucx_list_size(stmt->fields) == ucx_list_size(stmt->setvalues) - ? "yes" : "no"); - } // WITH attributes - if (stmt->depth < 0) { + 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); @@ -142,29 +156,41 @@ if (dav_debug_ql_expr_selected(expr)) { sstr_t empty = ST("(empty)"); printf( - "Text: %.*s\nType: %s\nOperator: %s\n" - "Left hand: %.*s\nRight hand: %.*s\n", + "Text: %.*s\nType: %s\nOperator: %s\n", sfmtarg(expr->srctext), _map_exprtype(expr->type), - _map_operator(expr->op), - sfmtarg(expr->left?expr->left->srctext:empty), - sfmtarg(expr->right?expr->right->srctext:empty)); + _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)); + } } } #define DQLD_CMD_Q 0 #define DQLD_CMD_PS 1 #define DQLD_CMD_PE 2 -#define DQLD_CMD_P 10 +#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[16]; - fgets(buffer, 16, stdin); + 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")) { @@ -177,6 +203,16 @@ 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; } @@ -196,6 +232,8 @@ } DavQLExpression *examineexpr = NULL; + UcxList *examineelem = NULL; + int examineclause = 0; while(1) { int cmd = dav_debug_ql_command(); @@ -203,6 +241,52 @@ 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_F: + if (examineclause != DQLD_CMD_F) { + examineclause = DQLD_CMD_F; + examineelem = stmt->fields; + examineexpr = stmt->fields ? + ((DavQLField*)stmt->fields->data)->expr : NULL; + dav_debug_ql_expr_print(examineexpr); + } + break; + case DQLD_CMD_W: + examineclause = 0; examineelem = NULL; + examineexpr = stmt->where; + dav_debug_ql_expr_print(examineexpr); + break; + case DQLD_CMD_O: + if (examineclause != 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; + } else if (examineclause == DQLD_CMD_F) { + examineexpr = ((DavQLField*)examineelem->data)->expr; + } else { + printf("Examining unknown clause type."); + } + dav_debug_ql_expr_print(examineexpr); + } 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) { @@ -227,6 +311,13 @@ printf( "\nCommands:\n" "ps: print statement information\n" + "o: examine order by clause\n" + "f: examine field list\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" @@ -245,12 +336,41 @@ #define _unexpected_end_msg "unexpected end of statement" #define _invalid_msg "invalid statement" #define _unexpected_token "unexpected token (%.*s [->]%.*s %.*s)" +#define _expected_token "expected token '%s' before '%.*s'" +#define _expected_by "expected 'by' after 'order' (order [->]%.*s)" +#define _missing_fmtspec "format specifier missing (%.*s [->]%.*s %.*s)" +#define _invalid_fmtspec "invalid format specifier (%.*s [->]%.*s %.*s)" +#define _unknown_fmtspec "unknown format specifier (%.*s [->]%.*s %.*s)" #define _missing_quote "missing closing quote symbol (%.*s)" +#define _parser_state "parser reached invalid state" +#define _unknown_attribute "unknown attribute '%.*s'" +#define _duplicated_attribute "duplicated attribute '%.*s'" +#define _invalid_depth "invalid depth" +#define _invalid_path "invalid path" + +#define _identifier_expected "identifier expected (%.*s [->]%.*s %.*s)" +#define _idornum_expected "identifier or number expected (%.*s [->]%.*s %.*s)" +#define _idorstr_expected "identifier or string expected (%.*s [->]%.*s %.*s)" +#define _idorts_expected "identifier or timestamp expected (%.*s [->]%.*s %.*s)" + +#define token_sstr(listelem) ((sstr_t*)(listelem)->data) + +static void dav_error_in_context(int errorcode, const char *errormsg, + DavQLStatement *stmt, UcxList *token) { + sstr_t emptystring = ST(""); + stmt->errorcode = errorcode; + stmt->errormessage = ucx_sprintf(errormsg, + sfmtarg(token->prev?*token_sstr(token->prev):emptystring), + sfmtarg(*token_sstr(token)), + sfmtarg(token->next?*token_sstr(token->next):emptystring)).ptr; +} + +// special symbols are single tokens - the % sign MUST NOT be a special symbol +static const char *special_token_symbols = ",()+-*/&|^~=!<>"; static UcxList* dav_parse_tokenize(sstr_t src) { UcxList *tokens = NULL; - // Delimiters: whitespace and dead whitespace around commas sstr_t *token = NULL; char insequence = '\0'; for (size_t i = 0 ; i < src.length ; i++) { @@ -283,13 +403,13 @@ tokens = ucx_list_append(tokens, token); token = NULL; } - } else if (src.ptr[i] == ',') { - // add token before comma to list (if any) + } else if (strchr(special_token_symbols, src.ptr[i])) { + // add token before special symbol to list (if any) if (token) { tokens = ucx_list_append(tokens, token); token = NULL; } - // add comma as token to list + // add special symbol as single token to list token = malloc(sizeof(sstr_t)); token->ptr = src.ptr + i; token->length = 1; @@ -315,7 +435,6 @@ return tokens; } -#define token_sstr(listelem) ((sstr_t*)(listelem)->data) static DavQLExpression* dav_parse_expression( DavQLStatement* stmt, UcxList* starttoken, size_t n) { if (n == 0) { @@ -332,8 +451,27 @@ expr->srctext.length = token_sstr(starttoken)->length; char firstchar = expr->srctext.ptr[0]; char lastchar = expr->srctext.ptr[expr->srctext.length-1]; - if (firstchar == '\'' || isdigit(firstchar)) { - expr->type = DAVQL_LITERAL; + if (firstchar == '\'') { + expr->type = DAVQL_STRING; + } else if (isdigit(firstchar)) { + expr->type = DAVQL_NUMBER; + } else if (firstchar == '%') { + if (expr->srctext.length == 1) { + dav_error_in_context(DAVQL_ERROR_MISSING_FMTSPEC, + _missing_fmtspec, stmt, starttoken); + } else if (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_UNKNOWN_FMTSPEC, + _unknown_fmtspec, stmt, starttoken); + } + } else { + dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC, + _invalid_fmtspec, stmt, starttoken); + } } else { expr->type = DAVQL_IDENTIFIER; } @@ -367,6 +505,7 @@ // process tokens for (size_t i = 0 ; i < n ; i++) { + sstr_t tokendata = *token_sstr(token); // TODO: make it so @@ -393,107 +532,437 @@ } free(expr); } + +#define _step_fieldlist_ 10 // field list +#define _step_FROM_ 20 // FROM clause +#define _step_WITH_ 30 // WITH clause +#define _step_WHERE_ 40 // WHERE clause +#define _step_ORDER_BYopt_ 552 // expecting more ORDER BY details or end +#define _step_ORDER_BY_ 50 // ORDER BY clause +#define _step_end_ 500 // expect end -static void dav_parse_unexpected_token(DavQLStatement *stmt, UcxList *token) { - sstr_t emptystring = ST(""); - stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN; - sstr_t errormsg = ucx_sprintf(_unexpected_token, - sfmtarg(token->prev?*token_sstr(token->prev):emptystring), - sfmtarg(*token_sstr(token)), - sfmtarg(token->next?*token_sstr(token->next):emptystring)); - stmt->errormessage = errormsg.ptr; +struct fieldlist_parser_state { + UcxList *expr_firsttoken; + DavQLField *currentfield; + size_t expr_len; + /* + * 0: begin of field list - may encounter "*" or "-" special fields + * 1: collect expression token + * switch to step 2 on keyword "as" + * expect "," or "from" only if expr_len is 1 (add to list and continue) + * 2: expect one token (identifier) for as clause + * 3: expect a ",": continue with step 1 + * or a "from": leave field list parser + * 4: expect end of field list (i.e. a "from" keyword) + */ + int step; +}; + +static void dav_free_field(DavQLField *field) { + dav_free_expression(field->expr); + free(field); +} + +static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token, + struct fieldlist_parser_state *state) { + sstr_t tokendata = *token_sstr(token); + + _Bool fromkeyword = !sstrcasecmp(tokendata, S("from")); + _Bool comma = !sstrcmp(tokendata, S(",")); + + switch (state->step) { + case 0: + if (!sstrcmp(tokendata, S("*")) || !sstrcmp(tokendata, S("-"))) { + DavQLField *field = malloc(sizeof(DavQLField)); + field->name = tokendata; + field->expr = calloc(1, sizeof(DavQLExpression)); + field->expr->type = DAVQL_IDENTIFIER; + field->expr->srctext = tokendata; + stmt->fields = ucx_list_append(stmt->fields, field); + + if (tokendata.ptr[0] == '-') { + // no further fields may follow, if dash symbol has been found + state->step = 4; + } else { + state->step = 3; + } + return _step_fieldlist_; + } + // did not encounter special field, fall through to step 1 + state->step = 1; + case 1: + if (fromkeyword || comma) { + // add possible identifier to list + if (state->expr_firsttoken) { + // TODO: skip comma in function call) + if (state->expr_len > 1) { + stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN; + stmt->errormessage = ucx_sprintf(_expected_token, + "AS", sfmtarg(tokendata)).ptr; + return 0; + } + + DavQLExpression *expr = dav_parse_expression( + stmt, state->expr_firsttoken, state->expr_len); + + if (expr->type != DAVQL_IDENTIFIER) { + dav_free_expression(expr); + stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN; + stmt->errormessage = ucx_sprintf(_expected_token, + "AS", sfmtarg(tokendata)).ptr; + return 0; + } // TODO: do not allow identifier when wildcard is present + + DavQLField *field = malloc(sizeof(DavQLField)); + field->expr = expr; + field->name = field->expr->srctext; + stmt->fields = ucx_list_append(stmt->fields, field); + + state->expr_firsttoken = NULL; + state->expr_len = 0; + + if (fromkeyword) { + return _step_FROM_; + } + } else { + dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN, + _unexpected_token, stmt, token); + return 0; + } + } else if (!sstrcasecmp(tokendata, S("as"))) { + // TODO: return error, if expr_first_token is NULL + state->currentfield = malloc(sizeof(DavQLField)); + state->currentfield->expr = dav_parse_expression( + stmt, state->expr_firsttoken, state->expr_len); + + state->expr_firsttoken = NULL; + state->expr_len = 0; + + state->step = 2; + } else { + // collect tokens for field expression + if (state->expr_firsttoken) { + state->expr_len++; + } else { + state->expr_firsttoken = token; + state->expr_len = 1; + } + } + + return _step_fieldlist_; + case 2: { + DavQLExpression *expr = dav_parse_expression(stmt, token, 1); + if (expr->type == DAVQL_IDENTIFIER) { + state->currentfield->name = expr->srctext; + stmt->fields = ucx_list_append(stmt->fields, state->currentfield); + state->currentfield = NULL; + } else { + dav_free_field(state->currentfield); + dav_error_in_context(DAVQL_ERROR_IDENTIFIER_EXPECTED, + _identifier_expected, stmt, token); + + } + dav_free_expression(expr); + state->step = 3; + + return _step_fieldlist_; + } + case 3: + if (fromkeyword) { + return _step_FROM_; + } else if (comma) { + state->step = 1; + return _step_fieldlist_; + } else { + dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN, + _unexpected_token, stmt, token); + return 0; + } + case 4: + if (fromkeyword) { + return _step_FROM_; + } else { + stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN; + stmt->errormessage = ucx_sprintf(_expected_token, + "FROM", sfmtarg(tokendata)).ptr; + return 0; + } + } +} + +static int dav_parse_from(DavQLStatement *stmt, UcxList *token) { + sstr_t tokendata = *token_sstr(token); + + if (!sstrcasecmp(tokendata, S("with"))) { + return _step_WITH_; + } else if (!sstrcasecmp(tokendata, S("where"))) { + return _step_WHERE_; + } else if (!sstrcasecmp(tokendata, S("order"))) { + return _step_ORDER_BY_; + } else { + if (stmt->path.ptr) { + if (stmt->path.ptr[0] == '/') { + char *end = tokendata.ptr+tokendata.length; + stmt->path.length = end - stmt->path.ptr; + } else { + stmt->errorcode = DAVQL_ERROR_INVALID_PATH; + stmt->errormessage = strdup(_invalid_path); + } + } else { + if (tokendata.ptr[0] == '/' || !sstrcmp(tokendata, S("%s"))) { + stmt->path = tokendata; + } else { + stmt->errorcode = DAVQL_ERROR_INVALID_PATH; + stmt->errormessage = strdup(_invalid_path); + } + } + return _step_FROM_; + } +} + +struct with_parser_state { + /* + * 0: key + * 1: = + * 2: value + * 3: comma or new clause + */ + int step; + /* + * 1: depth + */ + int key; + int keymask; +}; + +static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token, + struct with_parser_state *state) { + sstr_t tokendata = *token_sstr(token); + + switch (state->step) { + case 0: + if (!sstrcasecmp(tokendata, S("depth"))) { + state->key = 1; + state->step = 1; + } else { + stmt->errorcode = DAVQL_ERROR_UNKNOWN_ATTRIBUTE; + stmt->errormessage = ucx_sprintf(_unknown_attribute, + sfmtarg(tokendata)).ptr; + break; + } + if (state->keymask & state->key) { + stmt->errorcode = DAVQL_ERROR_DUPLICATED_ATTRIBUTE; + stmt->errormessage = ucx_sprintf(_duplicated_attribute, + sfmtarg(tokendata)).ptr; + } else { + state->keymask |= state->key; + } + return _step_WITH_; // continue parsing WITH clause + case 1: + if (sstrcmp(tokendata, S("="))) { + stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN; + stmt->errormessage = ucx_sprintf(_expected_token, + "=", sfmtarg(tokendata)).ptr; + } else { + state->step = 2; + } + return _step_WITH_; // continue parsing WITH clause + case 2: + switch (state->key) { + case 1: /* depth */ + if (!sstrcasecmp(tokendata, S("infinity"))) { + stmt->depth = DAV_DEPTH_INFINITY; + } else { + DavQLExpression *depthexpr = + dav_parse_expression(stmt, token, 1); + + 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); + char *chk; + memcpy(conv, depthstr.ptr, depthstr.length); + conv[depthstr.length] = '\0'; + stmt->depth = strtol(conv, &chk, 10); + if (*chk || stmt->depth < -1) { + stmt->errorcode = DAVQL_ERROR_INVALID_DEPTH; + stmt->errormessage = strdup(_invalid_depth); + } + free(conv); + } + } else { + stmt->errorcode = DAVQL_ERROR_INVALID_DEPTH; + stmt->errormessage = strdup(_invalid_depth); + } + + dav_free_expression(depthexpr); + } + break; + } + state->step = 3; + return _step_WITH_; // continue parsing WITH clause + case 3: + // a with clause may be continued with a comma + // or another clause may follow + if (!sstrcmp(tokendata, S(","))) { + state->step = 0; // reset clause parser + return _step_WITH_; + } else if (!sstrcasecmp(tokendata, S("where"))) { + return _step_WHERE_; + } else if (!sstrcasecmp(tokendata, S("order"))) { + return _step_ORDER_BY_; + } else { + dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN, + _unexpected_token, stmt, token); + return 0; + } + default: + stmt->errorcode = DAVQL_ERROR_INVALID; + stmt->errormessage = strdup(_parser_state); + return 0; + } +} + +struct orderby_parser_state { + /* + * 0: expect by keyword + * 1: expect identifier / number + * 2: expect asc / desc or comma + * 3: expect comma + */ + int step; + DavQLOrderCriterion *crit; +}; + +static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token, + struct orderby_parser_state *state) { + + sstr_t tokendata = *token_sstr(token); + + switch (state->step) { + case 0: + if (!sstrcasecmp(tokendata, S("by"))) { + state->step++; + } else { + stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN; + stmt->errormessage = ucx_sprintf(_expected_by, + sfmtarg(tokendata)).ptr; + } + return _step_ORDER_BY_; + case 1: + state->crit = malloc(sizeof(DavQLOrderCriterion)); + state->crit->column = dav_parse_expression(stmt, token, 1); + state->crit->descending = 0; + + if (!state->crit->column || ( + state->crit->column->type != DAVQL_NUMBER && + state->crit->column->type != DAVQL_IDENTIFIER)) { + free(state->crit); + dav_error_in_context(DAVQL_ERROR_IDORNUM_EXPECTED, + _idornum_expected, stmt, token); + } else { + stmt->orderby = ucx_list_append(stmt->orderby, state->crit); + } + + // continue parsing clause, if more tokens available + state->step++; + return _step_ORDER_BYopt_; + case 2: + if (!sstrcasecmp(tokendata, S("desc"))) { + state->crit->descending = 1; + } else if (!sstrcasecmp(tokendata, S("asc"))) { + state->crit->descending = 0; + } else if (!sstrcmp(tokendata, S(","))) { + state->step = 1; // reset clause parser + return _step_ORDER_BY_; // statement must not end now + } else { + dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN, + _unexpected_token, stmt, token); + return 0; + } + // continue parsing clause, if more tokens available + state++; + return _step_ORDER_BYopt_; + case 3: + if (!sstrcmp(tokendata, S(","))) { + state->step = 1; // reset clause parser + return _step_ORDER_BY_; // statement must not end now + } else { + dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN, + _unexpected_token, stmt, token); + return 0; + } + } + + return _step_end_; +} + +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); } static void dav_parse_get_statement(DavQLStatement *stmt, UcxList *tokens) { stmt->type = DAVQL_GET; + + int step = _step_fieldlist_; - /* - * 10: field list - * 20: FROM clause - * 520: expecting WHERE or WITH clause - * 30: WHERE clause - * 530: expecting WITH clause - * 40: WITH clause - * 500: ready to quit - * 999: error - * - */ - int step = 10; - - // Variables for token sublists for expressions - UcxList *exprstart = NULL; - size_t exprlen = 0; + struct with_parser_state state_with; + memset(&state_with, 0, sizeof(struct with_parser_state)); + struct orderby_parser_state state_orderby; + memset(&state_orderby, 0, sizeof(struct orderby_parser_state)); + struct fieldlist_parser_state state_fieldlist; + memset(&state_fieldlist, 0, sizeof(struct fieldlist_parser_state)); // Process tokens UCX_FOREACH(token, tokens) { - if (stmt->errorcode) { - ultrabreak: break; - } - - sstr_t tokendata = *token_sstr(token); - switch (step) { - // optional clauses - case 520: - if (!sstrcasecmp(tokendata, S("where"))) { - step = 30; - } - /* no break and no else*/ - case 530: - if (!sstrcasecmp(tokendata, S("with"))) { - step = 40; - } else { - dav_parse_unexpected_token(stmt, token); - goto ultrabreak; - } + // too much input data + case _step_end_: + dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN, + _unexpected_token, stmt, token); break; // field list - case 10: { - _Bool fromkeyword = !sstrcasecmp(tokendata, S("from")); - if (fromkeyword || !sstrcmp(tokendata, S(","))) { - if (exprstart) { - stmt->fields = ucx_list_append(stmt->fields, - dav_parse_expression(stmt, exprstart, exprlen)); - exprstart = NULL; - exprlen = 0; - } else { - // TODO: throw syntax error - } - - if (fromkeyword) { - step = 20; - } - } else { - // collect tokens for field expression - if (exprstart) { - exprlen++; - } else { - exprstart = token; - exprlen = 1; - } - } + case _step_fieldlist_: { + step = dav_parse_fieldlist(stmt, token, &state_fieldlist); break; } // from clause - case 20: { - DavQLExpression *expr = dav_parse_expression(stmt, token, 1); - stmt->path = expr->srctext; - dav_free_expression(expr); - step = 520; + case _step_FROM_: { + step = dav_parse_from(stmt, token); + break; + } + // with clause + case _step_WITH_: { + step = dav_parse_with_clause(stmt, token, &state_with); break; } // where clause - case 30: - step = 530; + case _step_WHERE_: + // TODO: implement + step = _step_end_; break; - // with clause - case 40: - step = 500; + // order by clause + case _step_ORDER_BY_: + case _step_ORDER_BYopt_: + step = dav_parse_orderby_clause(stmt, token, &state_orderby); + break; + default: + stmt->errorcode = DAVQL_ERROR_INVALID; + stmt->errormessage = strdup(_parser_state); + } + + // cancel processing, when an error has been detected + if (stmt->errorcode) { break; } } - if (step < 500) { + if (!stmt->errorcode && step < _step_end_) { stmt->errorcode = DAVQL_ERROR_UNEXPECTED_END; stmt->errormessage = strdup(_unexpected_end_msg); } @@ -553,13 +1022,9 @@ void dav_free_statement(DavQLStatement *stmt) { UCX_FOREACH(expr, stmt->fields) { - dav_free_expression(expr->data); + dav_free_field(expr->data); } ucx_list_free(stmt->fields); - UCX_FOREACH(expr, stmt->setvalues) { - dav_free_expression(expr->data); - } - ucx_list_free(stmt->setvalues); if (stmt->where) { dav_free_expression(stmt->where); @@ -567,5 +1032,9 @@ if (stmt->errormessage) { free(stmt->errormessage); } + UCX_FOREACH(crit, stmt->orderby) { + dav_free_order_criterion(crit->data); + } + ucx_list_free(stmt->orderby); free(stmt); } diff -r f4127c4d1018 -r 95a215337b53 libidav/davqlparser.h --- a/libidav/davqlparser.h Sat May 02 10:59:02 2015 +0200 +++ b/libidav/davqlparser.h Sat May 02 11:00:28 2015 +0200 @@ -46,7 +46,8 @@ * Enumeration of possible expression types. */ typedef enum { - DAVQL_LITERAL, DAVQL_IDENTIFIER, + DAVQL_UNDEFINED_TYP, + DAVQL_NUMBER, DAVQL_STRING, DAVQL_TIMESTAMP, DAVQL_IDENTIFIER, DAVQL_UNARY, DAVQL_BINARY, DAVQL_LOGICAL, DAVQL_FUNCCALL } davqlexprtype_t; @@ -54,12 +55,12 @@ * Enumeration of possible expression operators. */ typedef enum { - DAVQL_NOOP, + DAVQL_NOOP, DAVQL_CALL, DAVQL_ARGLIST, // internal representations DAVQL_ADD, DAVQL_SUB, DAVQL_MUL, DAVQL_DIV, - DAVQL_AND, DAVQL_OR, DAVQL_XOR, DAVQL_NEG, - DAVQL_NOT, DAVQL_LAND, DAVQL_LOR, DAVQL_LXOR, + DAVQL_AND, DAVQL_OR, DAVQL_XOR, DAVQL_NEG, // airthmetic + DAVQL_NOT, DAVQL_LAND, DAVQL_LOR, DAVQL_LXOR, // logical DAVQL_EQ, DAVQL_NEQ, DAVQL_LT, DAVQL_GT, DAVQL_LE, DAVQL_GE, - DAVQL_LIKE, DAVQL_UNLIKE + DAVQL_LIKE, DAVQL_UNLIKE // comparisons } davqloperator_t; /** @@ -82,7 +83,6 @@ davqlexprtype_t type; /** * Operator. - * */ davqloperator_t op; /** @@ -97,6 +97,42 @@ DavQLExpression *right; }; +/** + * A tuple representing an order criterion. + */ +typedef struct { + /** + * The column. + */ + DavQLExpression *column; + /** + * True, if the result shall be sorted descending, false otherwise. + * Default is false (ascending). + */ + _Bool descending; +} DavQLOrderCriterion; + +/** + * A tuple representing a field. + */ +typedef struct { + /** + * The field name. + * + */ + sstr_t name; + /** + * The field expression. + * + */ + DavQLExpression *expr; +} DavQLField; /** * Query statement object. @@ -110,13 +146,15 @@ * | FunctionCall | Identifier | Literal * | "(", Expression, ")"; * - * FunctionCall = Identifier, "(", Expression, ")"; + * FunctionCall = Identifier, "(", ArgumentList, ")"; + * ArgumentList = Expression, {",", Expression}; * Identifier = IdentifierChar - ?Digit?, {IdentifierChar} - * | "`", ?Character?, {?Character?}, "`"; - * IdentifierChar = ?Character - (" "|",")?; - * Literal = Number | String; + * | "`", ?Character? - "`", {?Character? - "`"}, "`"; + * IdentifierChar = ?Character? - (" "|","); + * Literal = Number | String | Timestamp; * Number = ?Digit?, {?Digit?} | "%d"; - * String = "'", {?Character - "'"? | "'''"} , "'"; + * String = "'", {?Character? - "'" | "'''"} , "'" | "%s"; + * Timestamp = "%t"; // TODO: maybe introduce a real literal * * LogicalExpression = LogicalExpression, LogicalOperator, LogicalExpression * | "not ", LogicalExpression @@ -129,16 +167,23 @@ * LogicalOperator = " and " | " or " | " xor "; * Comparison = | "=" | "<" | ">" | "<=" | ">=" | "!="; * - * FieldExpressions = "*", {",", Expression, " as ", String} + * FieldExpressions = "*", {",", Expression, " as ", Identifier} * | FieldExpression, {",", FieldExpression} * | "-"; * FieldExpression = Identifier - * | Expression, " as ", String; - * SetExpressions = SetExpression, {",", {SetExpressions}; + * | Expression, " as ", Identifier; + * SetExpressions = SetExpression, {",", SetExpressions}; * SetExpression = Identifier, "=", Expression; * + * Path = "%s" + * | "/", {?Character? - " "} + * | "'/", {?Character?}, "'"; + * * WithClause = "depth", "=", (Number | "infinity"); * + * OrderByClause = OrderByCriterion, {",", OrderByCriterion}; + * OrderByCriterion = (Identifier | Number), [" asc"|" desc"]; + * * * * Note: mandatory spaces are part of the grammar. But you may also insert an @@ -148,17 +193,18 @@ * GET: *
  * GetStatement = "get ", FieldExpressions,
- * " from ", Identifier,
+ * " from ", Path,
+ * [" with ", WithClause],
  * [" where ", LogicalExpression],
- * [" with ", WithClause];
+ * [" order by ", OrderByClause];
   * 
* * SET: *
  * "set ",SetExpressions,
- * " at ", Identifier,
- * (" where ", LogicalExpression) | " anywhere",
- * [" with ", WithClause];
+ * " at ", Path,
+ * [" with ", WithClause],
+ * (" where ", LogicalExpression) | " anywhere";
  * 
* */ @@ -180,15 +226,10 @@ */ char* errormessage; /** - * The list of field expressions. + * The list of DavQLFields. */ UcxList* fields; /** - * The list of DavQLExpressions for the new DAV property values. - * This is NULL for GET queries. - */ - UcxList* setvalues; - /** * A string that denotes the queried path. */ sstr_t path; @@ -198,8 +239,16 @@ */ DavQLExpression* where; /** + * The list of DavQLOrderCriterions. + * This is NULL for SET queries and may be NULL + * if the result doesn't need to be sorted. + */ + UcxList* orderby; + /** * The recursion depth for the statement. * Defaults to 1. + * Magic numbers are DAV_DEPTH_INFINITY for infinity and + * DAV_DEPTH_PLACEHOLDER for a placeholder. */ int depth; } DavQLStatement; @@ -207,6 +256,45 @@ /** Infinity recursion depth for a DavQLStatement. */ #define DAV_DEPTH_INFINITY -1 +/** Depth needs to be specified at runtime. */ +#define DAV_DEPTH_PLACEHOLDER -2 + +/** Invalid path. */ +#define DAVQL_ERROR_INVALID_PATH 1 + +/** Expected an identifier, but found something else. */ +#define DAVQL_ERROR_IDENTIFIER_EXPECTED 10 + +/** Expected an identifier or literal, but found something else. */ +#define DAVQL_ERROR_IDORLIT_EXPECTED 11 + +/** Expected an identifier or number, but found something else. */ +#define DAVQL_ERROR_IDORNUM_EXPECTED 12 + +/** Expected an identifier or string, but found something else. */ +#define DAVQL_ERROR_IDORSTR_EXPECTED 13 + +/** Expected an identifier or timestamp, but found something else. */ +#define DAVQL_ERROR_IDORTS_EXPECTED 14 + +/** The with-clause contains an unknown attribute. */ +#define DAVQL_ERROR_UNKNOWN_ATTRIBUTE 20 + +/** Depth must be greater than zero or infinity. */ +#define DAVQL_ERROR_INVALID_DEPTH 21 + +/** The with-clause contains an attribute more than once. */ +#define DAVQL_ERROR_DUPLICATED_ATTRIBUTE 29 + +/** The format specifier is missing. */ +#define DAVQL_ERROR_MISSING_FMTSPEC 30 + +/** The format specifier is unknown. */ +#define DAVQL_ERROR_UNKNOWN_FMTSPEC 31 + +/** The format specifier is invalid. */ +#define DAVQL_ERROR_INVALID_FMTSPEC 39 + /** A quote symbol (' or `) is missing. */ #define DAVQL_ERROR_MISSING_QUOTE 50