cdk/parser/expr_parser.cc (1,154 lines of code) (raw):

/* * Copyright (c) 2015, 2024, Oracle and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, as * published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * * Without limiting anything contained in the foregoing, this file, * which is part of Connector/C++, is also subject to the * Universal FOSS Exception, version 1.0, a copy of which can be found at * https://oss.oracle.com/licenses/universal-foss-exception. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "expr_parser.h" PUSH_SYS_WARNINGS_CDK #include <stdlib.h> POP_SYS_WARNINGS_CDK using namespace parser; using cdk::Expression; typedef cdk::Expression::Processor Processor; typedef Processor::List_prc List_prc; using cdk::Safe_prc; using cdk::safe_prc; /* Set up keyword and operator maps. */ Keyword::map_t Keyword::kw_map; Keyword Keyword::init; Op::tok_map_t Op::unary_tok_map, Op::binary_tok_map; Op::kw_map_t Op::unary_kw_map, Op::binary_kw_map; Op Op::init; // ------------------------------------------------------------------------- /* Variant of std::auto_ptr such that after smart_ptr.release() the pointed object can still be accessed via smart_ptr->xxx() (even though it is no longer owned by this smart_ptr instance). */ template <typename T> class smart_ptr : public cdk::foundation::nocopy { T *m_ptr; bool m_owns; public: smart_ptr(T *ptr = NULL) : m_ptr(ptr), m_owns(true) {} ~smart_ptr() { reset(NULL); } void operator=(T *ptr) { reset(ptr); } T* reset(T *ptr) { if (m_owns) delete m_ptr; m_ptr = ptr; m_owns = true; return ptr; } T* release() { m_owns = false; return m_ptr; } T* operator->() { return m_ptr; } }; /* Sink expression processor that ignores the expression reported to it. It is used below in situations where we want to ignore results of parsing without storing them anywhere. */ struct Sink : public Expression::Processor { Scalar_prc* scalar() { return NULL; } List_prc* arr() { return NULL; } Doc_prc* doc() { return NULL; } }; Expression::Processor* ignore_if(Expression::Processor *prc) { static Sink sink; if (!prc) return &sink; return prc; } bool Expr_parser_base::do_parse(Processor *prc) { /* if prc is NULL, ignore the parsed expression instead of storing it which would be the case if we pass NULL to parse(). For safety, delete the object returned from parse() if any. */ delete parse(FULL, ignore_if(prc)); return true; } // ------------------------------------------------------------------------- /** castOp ::= CAST LPAREN expr AS castType RPAREN */ bool Expr_parser_base::parse_cast(Scalar_prc *prc) { if (!consume_token(Op::CAST)) return false; Safe_prc<List_prc> ap = safe_prc(prc)->op(Op::name(Op::CAST)); consume_token_throw(Token::LPAREN, "Expected '(' after CAST"); ap->list_begin(); // 1st arg, the expression delete parse(FULL, ignore_if(ap->list_el())); consume_token_throw(Keyword::AS, "Expected AS after expression inside CAST operator"); // 2nd arg, cast_data_type ap->list_el()->scalar()->val()->value(cdk::TYPE_BYTES, Format_info(), cdk::bytes(parse_cast_type())); ap->list_end(); consume_token_throw(Token::RPAREN, "Expected ')' closing CAST operator call"); return true; } /** castType ::= SIGNED INTEGER? | UNSIGNED INTEGER? | CHAR lengthSpec? | BINARY lengthSpec? | DECIMAL (lengthSpec | (LPAREN INT COMMA INT RPAREN))? | TIME | DATE | DATETIME | JSON lengthSpec ::= LPAREN INT RPAREN */ std::string Expr_parser_base::parse_cast_type() { std::string type_str; const Token* token = consume_token(); if (!token) parse_error("Expected cast type"); Keyword::Type type = Keyword::get(*token); if (Keyword::NONE == type) parse_error("Unexpected cast type"); type_str = Keyword::name(type); switch (type) { case Keyword::BINARY: case Keyword::CHAR: case Keyword::DECIMAL: if (cur_token_type_is(Token::LPAREN)) type_str += cast_data_type_dimension(Keyword::DECIMAL == type); break; case Keyword::SIGNED: case Keyword::UNSIGNED: consume_token(Keyword::Set{ Keyword::INTEGER, Keyword::INT }); type_str += " "; type_str += Keyword::name(Keyword::INTEGER); break; case Keyword::DATE: case Keyword::DATETIME: case Keyword::TIME: case Keyword::INTEGER: case Keyword::JSON: break; default: parse_error("Unexpected cast type"); } return type_str; } /** dimension ::= LPAREN LINTEGER RPAREN if double_dimention = true: LPAREN INT COMMA INT RPAREN returns textual representation of the parse, like "(N)" or "(N:M)". */ std::string Expr_parser_base::cast_data_type_dimension(bool double_dimension) { consume_token_throw(Token::LPAREN, "Expected type dimension specification"); std::string result("("); result += consume_token_throw( Token::INTEGER, "Expected integer type dimension" ).get_utf8(); if (double_dimension && consume_token(Token::COMMA)) { result += ","; result += consume_token_throw( Token::INTEGER, "Expected second type dimension after ','" ).get_utf8(); } result += ")"; consume_token_throw( Token::RPAREN, "Expected ')' closing type dimension specification" ); return result; } // ------------------------------------------------------------------------- /* ident ::= ID | QUOTED_ID */ bool Expr_parser_base::get_ident(string &id) { if (!tokens_available()) return false; if (Token_base::cur_token_type_in({ Token::WORD, Token::QWORD })) { id = consume_token()->get_utf8(); return true; } return false; } /* Assuming that a schema-qualified identifier was just parsed, attempt to parse a function call if next token starts argument list. Returns false if this is not the case. functionCall ::= schemaQualifiedIdent LPAREN argsList? RPAREN */ bool Expr_parser_base::parse_function_call(const cdk::api::Table_ref &func, Scalar_prc *prc) { if (!consume_token(Token::LPAREN)) return false; List_prc *aprc = NULL; bool qualified = (NULL != func.schema()); bool parse_position = false; // Report position(.. IN ..) as locate(...,...) if (! qualified && Keyword::equal(func.name(), "position")) { Table_ref locate; locate.set("locate"); aprc = safe_prc(prc)->call(locate); parse_position = true; } else aprc = safe_prc(prc)->call(func); if (aprc) aprc->list_begin(); if (!cur_token_type_is(Token::RPAREN)) { if ( !qualified && Keyword::equal(func.name(), "trim") && cur_token_type_in({ Keyword::BOTH, Keyword::LEADING, Keyword::TRAILING }) ) unsupported("LEADING, TRAILING or BOTH clause inside function TRIM()"); delete parse(parse_position ? COMP : FULL, aprc ? aprc->list_el() : NULL); if (consume_token(Token::COMMA)) parse_argslist(aprc); else parse_special_args(func, aprc); } if (aprc) aprc->list_end(); consume_token_throw( Token::RPAREN, "Expected ')' to close function argument list" ); return true; } void Expr_parser_base::parse_special_args( const cdk::api::Table_ref &func, Expression::List::Processor *aprc ) { if (func.schema()) return; if (Keyword::equal(func.name(), "char")) { if (cur_token_type_is(Keyword::USING)) unsupported("USING clause inside function CHAR()"); return; } if (Keyword::equal(func.name(), "trim")) { if (cur_token_type_is(Keyword::FROM)) unsupported("FROM clause inside function TRIM()"); } if (Keyword::equal(func.name(), "position")) { if (!consume_token(Keyword::IN)) parse_error("Expected IN inside POSITION(... IN ...)"); delete parse(FULL, aprc ? aprc->list_el() : NULL); return; } } /* Original grammar: // [[schema.]table.]ident columnIdent ::= (ident '.' (ident '.')?)? ident ('->' (('$' documentPath) | ("'$" documentPath "'")) )? is rewritten as: columnIdent ::= schemaQualifiedIdent columnIdent1 columnIdent1 ::= ('.' ident)? ('->' ( columnIdentDocPath | "'" columnIdentDocPath "'" ))? columnIdentDocPath ::= documentField // but require DOLLAR prefix */ /* Parse a schema-qualified identifier and store it as table/schema name of m_col_ref member. Schema name is optional. If types is not NULL then types of the consumed tokens are stored in this array. */ bool Expr_parser_base::parse_schema_ident(Token::Type (*types)[2]) { if (types) { (*types)[0] = Token::Type(0); (*types)[1] = Token::Type(0); } if (!tokens_available()) return false; if (types) (*types)[0] = peek_token()->get_type(); string name; if (!get_ident(name)) return false; m_col_ref.m_table_ref.set(name); if (consume_token(Token::DOT)) { if (!tokens_available()) return false; if (types) (*types)[1] = peek_token()->get_type(); string name1; if (!get_ident(name1)) return false; m_col_ref.m_table_ref.set(name1, name); } return true; } void Expr_parser_base::parse_column_ident(Processor *prc) { if (!parse_schema_ident()) parse_error("Expected a column identifier"); parse_column_ident1(prc); } void Expr_parser_base::parse_column_ident1(Processor *prc) { /* Note: at this point we assume that an (possibly schema qualified) identifier has been already seen and is stored in m_col_ref.table() */ if (consume_token(Token::DOT)) { string name; if (!get_ident(name)) parse_error("Expected identifier after '.'"); // Note: the table part was initialized in parse_schema_ident() m_col_ref.set_name(name); } else { // Re-interpret table name parsed by parse_schema_ident() as a // column name of the form [<table>.]<column> auto table = m_col_ref.table(); assert(table); if (table->schema()) m_col_ref.set(table->name(), table->schema()->name()); else m_col_ref.set(table->name()); } auto t = peek_token(); Safe_prc<Processor> sprc(prc); if (t && (t->get_type() == Token::ARROW || t->get_type() == Token::ARROW2)) { Safe_prc<cdk::Expr_processor::Args_prc> args = nullptr; if(t->get_type() == Token::ARROW2) { Table_ref json_unquote; json_unquote.set("JSON_UNQUOTE"); args =sprc->scalar()->call(json_unquote); args->list_begin(); //Will override previous processor, so from now on, this will be the one //used sprc = args->list_el(); } consume_token(); cdk::Doc_path_storage path; if (Token_base::cur_token_type_in({ Token::QSTRING, Token::QQSTRING })) { Tokenizer toks(consume_token()->get_bytes()); It first = toks.begin(); It last = toks.end(); Expr_parser_base path_parser(first, last, m_parser_mode); // TODO: Translate parse errors path_parser.parse_document_field(&path, true); if (first != last) parse_error("Unexpected characters in a quoted path component"); } else { parse_document_field(&path, true); } sprc->scalar()->ref(m_col_ref,&path); args->list_end(); } else { sprc->scalar()->ref(m_col_ref,nullptr); } } // ------------------------------------------------------------------------- /** The original grammar was: documentField ::= fieldId [documentPath] | "$" [ documentPath ] Which makes "*", "**.foo" or "*.foo" not valid field specifications while "$[3]" is a valid specification. We modify the grammar so that "$[..]" is not valid while "*.." or "**.." are valid: documentField ::= | DOLLAR documentPathLeadingDot? | documentPath The grammar of documentPath was adjusted so that the first path item can not be an array item ("[n]" or "[*]") and we can request a leading DOT before member items (see parse_document_path()). If prefix is true, only the first form starting with DOLLAR prefix is accepted. */ void Expr_parser_base::parse_document_field(Path_prc *prc, bool prefix) { if (consume_token(Token::DOLLAR)) { if (!parse_document_path(prc, true)) { // The "$" path which denotes the whole document. prc->whole_document(); } return; } if (prefix) parse_error("Expected '$' to start a document path"); if (!parse_document_path(prc, false)) parse_error("Expected a document path"); } /* Parse a document field path with a given initial member segment. */ void Expr_parser_base::parse_document_field(const string &first, Path_prc *prc) { Safe_prc<Path_prc> sprc = prc; sprc->list_begin(); sprc->list_el()->member(first); parse_document_path1(prc); sprc->list_end(); } /* Parse a document field path with given 2 initial member segment. */ void Expr_parser_base::parse_document_field(const string &first, const string &second, Path_prc *prc) { Safe_prc<Path_prc> sprc = prc; sprc->list_begin(); sprc->list_el()->member(first); sprc->list_el()->member(second); parse_document_path1(prc); sprc->list_end(); } /** Original Grammar: documentPath ::= documentPathItem* documentPathLastItem documentPathItem ::= documentPathLastItem | DOUBLESTAR documentPathLastItem ::= ARRAYSTAR | LSQBRACKET INT RSQBRACKET | DOTSTAR | DOT documentPathMember documentPathMember ::= ID | STRING1 This grammar has few flaws: 1. It allows a document path to start with array location, which is not correct - array locations should be possible only after a path to some array member. 2. It always requires a DOT before a member element, but in some contexts we want a document path like "foo.bar.baz" to start without a dot. To deal with this the grammar has been changed and require_dot parameter has been added. Modified grammar: documentPath ::= documentPathFirstItem documentPathItem* documentPathFirstItem ::= | DOT? documentPathMember | DOUBLESTAR documentPathItem ::= | DOT documentPathMember | DOUBLESTAR | documentPathArray documentPathMember ::= | MUL | ID | STRING1 docuemntPathArray ::= LSQBRACKET documentPathArrayLoc RSQBRACKET documentPathArrayLoc ::= | MUL | INT Parameter require_dot tells if the initial dot is required or not. A check that DOUBLESTAR is not last element of a path is done separately. Returns true if a valid document path was parsed and reported, false if the current token did not start a valid document path. Note: If false is returned then nothing is reported to the processor (not even an empty list). */ bool Expr_parser_base::parse_document_path(Path_prc *prc, bool require_dot) { /* Below we call methods like parse_docpath_member() which expect a document path element processor. Our path processor prc is a list processor. So, before we report the first path element we must call prc->list_begin() and prc->list_el(). The problem is that when calling parse_docpath_member() we might not know yet if there is any path to report or not -- only inside parse_docpath_member() it will become evident. The Path_el_reporter wrapper around path processor solves this problem by deferring the initial list_begin() call and the list_el() calls to the moment when a path element is reported. If no path elements are reported then list_begin() or list_el() will not be called. Similar, call to list_end() will be forwarded to the wrapped processor only if list_begin() was called before. */ struct Path_el_reporter : public Path_prc , public Path_prc::Element_prc { using Element_prc::string; using Element_prc::index_t; Safe_prc<Path_prc> m_prc; bool m_started; void list_begin() { if (!m_started) m_prc->list_begin(); m_started = true; } void list_end() { if (m_started) m_prc->list_end(); } Element_prc* list_el() { return this; } // Element_prc void member(const string &name) { list_begin(); m_prc->list_el()->member(name); } void any_member() { list_begin(); m_prc->list_el()->any_member(); } void index(index_t ind) { list_begin(); m_prc->list_el()->index(ind); } void any_index() { list_begin(); m_prc->list_el()->any_index(); } void any_path() { list_begin(); m_prc->list_el()->any_path(); } void whole_document() { m_prc->whole_document(); } Path_el_reporter(Path_prc *prc) : m_prc(prc), m_started(false) {} } el_reporter(prc); // documentPathFirstItem bool double_star = false; if (consume_token(Token::DOUBLESTAR)) { double_star = true; el_reporter.any_path(); } else if (parse_docpath_member_dot(&el_reporter)) { // continue below } else if (require_dot) { return false; } else { if (!parse_docpath_member(&el_reporter)) return false; } // the rest of the path (if any) bool ret = parse_document_path1(&el_reporter); if (!ret && double_star) parse_error("Document path ending in '**'"); el_reporter.list_end(); return true; } /* Parse a reminder of a document path after the first item, that is, a possibly empty sequence of documentPathItem strings. The items are reported to the given Path_prc without calling list_begin() or list_end() (which is assumed to be done by the caller). Returns true if at least one path item component was parsed. */ bool Expr_parser_base::parse_document_path1(Path_prc *prc) { Safe_prc<Path_prc> sprc = prc; /* These Booleans are used to detect if we are at the beginning of the path and if there was a "**" component at the end of it. */ bool double_star; bool last_double_star = false; bool has_item = false; for (double_star = false; true; last_double_star = double_star, double_star =false, has_item = true) { if (!cur_token_type_in({ Token::DOUBLESTAR, Token::DOT, Token::LSQBRACKET })) break; if (consume_token(Token::DOUBLESTAR)) { sprc->list_el()->any_path(); double_star = true; continue; } if (parse_docpath_member_dot(sprc)) continue; if (parse_docpath_array(sprc)) continue; break; } if (last_double_star) parse_error("Document path ending in '**'"); return has_item; } /** documentPathMember ::= | MUL | ID | STRING1 TODO: Does STRING1 differ from plain STRING in any way? */ bool Expr_parser_base::parse_docpath_member(Path_prc *prc) { const Token *t = peek_token(); if (!t) return false; switch (t->get_type()) { case Token::STAR: safe_prc(prc)->list_el()->any_member(); break; case Token::WORD: case Token::QQSTRING: case Token::QSTRING: safe_prc(prc)->list_el()->member(t->get_text()); break; default: return false; } consume_token(); return true; } bool Expr_parser_base::parse_docpath_member_dot(Path_prc *prc) { if (!consume_token(Token::DOT)) return false; if (!parse_docpath_member(prc)) parse_error("Expected member name or '*' after '.' in a document path"); return true; } /** docuemntPathArray ::= LSQBRACKET documentPathArrayLoc RSQBRACKET documentPathArrayLoc ::= | MUL | INT */ bool Expr_parser_base::parse_docpath_array(Path_prc *prc) { if (!consume_token(Token::LSQBRACKET)) return false; if (consume_token(Token::STAR)) { safe_prc(prc)->list_el()->any_index(); } else { if (!cur_token_type_is(Token::INTEGER)) parse_error("Expected '*' or integer index after '[' in a document path"); uint64_t v; try { v = strtoui(consume_token()->get_utf8()); } catch (const Numeric_conversion_error &e) { parse_error(e.msg()); throw; // quiet compile warnings } if (v > std::numeric_limits<Path_prc::Element_prc::index_t>::max()) parse_error("Array index too large"); safe_prc(prc)->list_el()->index(Path_prc::Element_prc::index_t(v)); } consume_token_throw( Token::RSQBRACKET, "Expected ']' to close a document path array component" ); return true; } // ------------------------------------------------------------------------- bool column_ref_from_path(cdk::Doc_path &path, parser::Column_ref &column) { struct Path_prc : public cdk::Doc_path::Processor , public cdk::Doc_path::Processor::Element_prc { unsigned m_len; parser::Column_ref &m_col; bool m_ret; Element_prc* list_el() { return this; } void member(const Element_prc::string &name) { switch (m_len++) { case 0: m_col.set(name); break; case 1: m_col.set(name, m_col.name()); break; case 2: assert(m_col.table()); m_col.m_table_ref.set(m_col.name(), m_col.table()->name()); m_col.set_name(name); break; default: // Too many path elements m_ret = false; } } void index(uint32_t) { m_ret = false; } void any_member() { m_ret = false; } void any_index() { m_ret = false; } void any_path() { m_ret = false; } void whole_document() { m_ret = false; } Path_prc(parser::Column_ref &col) : m_len(0), m_col(col), m_ret(true) {} } prc(column); path.process(prc); return prc.m_ret; } /** atomicExpr ::= placeholder | columnIdent // TABLE mode | documentField // DOCUMENT mode | functionCall | groupedExpr | unaryOp | castOp | literal | jsonDoc | array placeholder ::= COLON ID groupedExpr ::= LPAREN expr RPAREN unaryOp ::= BANG atomicExpr | NEG atomicExpr | PLUS atomicExpr | MINUS atomicExpr literal ::= INT | FLOAT | STRING1 | STRING2 | NULL | FALSE | TRUE We extend this grammar with nullary operators: nullaryOp ::= MUL TODO: "default" operator */ Expression* Expr_parser_base::parse_atomic(Processor *prc) { if (!tokens_available()) parse_error("Expected an expression"); Token::Type type = peek_token()->get_type(); switch (type) { // jsonDOC case Token::LCURLY: return parse(DOC, prc); // array case Token::LSQBRACKET: return parse(ARR, prc); // groupedExpr case Token::LPAREN: { consume_token(); smart_ptr<Expression> res(parse(FULL, prc)); consume_token_throw( Token::RPAREN, "Expected ')' to close parenthesized sub-expression" ); return res.release(); } default: break; } /* If prc is NULL, we are supposed to store and return the result of parsing. In that case initialize stored variable with appropriate storage object and set prc to point at it so that expression will be reported to the storage object. Note: if prc is not NULL then stored remains empty and stored.release() would produce NULL as required in this case. */ smart_ptr<Stored_expr> stored; if (!prc) prc = stored.reset(new Stored_any()); Safe_prc<Processor> sprc(prc); // parameters, nullary operators, CAST if (consume_token(Token::COLON)) { sprc->scalar()->param(consume_token_throw( Token::WORD, "Expected parameter name after ':'" ).get_text()); return stored.release(); } if (consume_token(Op::STAR)) { sprc->scalar()->op(Op::name(Op::STAR)); // NOTE: arguments processor is ignored as there are no arguments return stored.release(); } if (parse_cast(prc->scalar())) { return stored.release(); } // Unary operator. List_prc *argsp = NULL; bool neg = false; Op::Type op = Op::get_unary(*peek_token()); switch (op) { case Op::PLUS: case Op::MINUS: { consume_token(); if (Token_base::cur_token_type_in({ Token::NUMBER, Token::INTEGER })) { // treat as numeric literal with possibly negated value neg = (Op::MINUS == op); break; } // otherwise report as unary operator argsp = sprc->scalar()->op(Op::name(op)); break; } case Op::NEG: consume_token(); argsp = sprc->scalar()->op(Op::name(Op::NEG)); break; case Op::NOT: consume_token(); argsp = sprc->scalar()->op(Op::name(Op::NOT)); break; case Op::BITNEG: consume_token(); argsp = sprc->scalar()->op(Op::name(Op::BITNEG)); break; default: break; // will continue with literal parsing } // Report the single argument of the unary operator if (argsp) { argsp->list_begin(); delete parse(ATOMIC, argsp->list_el()); argsp->list_end(); return stored.release(); } assert(tokens_available()); // Literal value Keyword::Type kw = Keyword::get(*peek_token()); switch (kw) { case Keyword::L_NULL: sprc->scalar()->val()->null(); consume_token(); return stored.release(); case Keyword::L_TRUE: case Keyword::L_FALSE: sprc->scalar()->val()->yesno(Keyword::L_TRUE == kw); consume_token(); return stored.release(); default: // continue looking for other literals break; } try { switch (peek_token()->get_type()) { case Token::QQSTRING: case Token::QSTRING: if (m_strings_as_blobs) { sprc->scalar()->val()->value( cdk::TYPE_BYTES, Format_info(), consume_token()->get_bytes() ); } else sprc->scalar()->val()->str(consume_token()->get_text()); return stored.release(); case Token::NUMBER: { double val = strtod(consume_token()->get_utf8()); sprc->scalar()->val()->num(neg ? -val : val); return stored.release(); } case Token::INTEGER: if (neg) { int64_t val = strtoi(consume_token()->get_utf8()); sprc->scalar()->val()->num(-val); } else { uint64_t val = strtoui(consume_token()->get_utf8()); sprc->scalar()->val()->num(val); } return stored.release(); case Token::HEX: if (neg) { int64_t val = strtoi(consume_token()->get_utf8(), 16); sprc->scalar()->val()->num(-val); } else { uint64_t val = strtoui(consume_token()->get_utf8(), 16); sprc->scalar()->val()->num(val); } return stored.release(); default: // will continue with functionCall | columnIdent | documentField parsing break; } } catch (const Numeric_conversion_error &e) { parse_error(e.msg()); } /* functionCall | columnIdent | documentField It is not possible to tell which of these 3 alternatives we have by looking at the current token. Either functionCall or columnIdent or documentField can start with something which looks like a schema-qualified name: "A" or "A.B". For that reason we start with a call to parse_schema_indent() which would parse such a schema-qualified name and store it as table/schema name of m_col_ref member. After this we try to parse a function call and if it fails we try columnIndent or documentField, depending on the parsing mode. */ Token::Type types[2]; bool schema_ident = false; m_col_ref.clear(); /* Try to parse schema-qualified identifier, storing the types of the tokens that have been consumed - this information is needed in case parsing schema identifier fails. Note: it is important that parse_schema_ident() stores consumed tokens in m_col_ref even if it fails in the end. */ schema_ident = parse_schema_ident(&types); /* If parse_schema_ident() succeeded, and we have the result in m_col_ref.table(), we see if it is not a beginning of a function call. If parse_function_call() succeeds then we are done. */ if (schema_ident) { assert(m_col_ref.table()); if (parse_function_call(*m_col_ref.table(), sprc.scalar())) return stored.release(); } /* Otherwise we must have either a document path (in DOCUMENT mode) or a column identifier, possibly followed by a path (in TABLE mode). */ if (Parser_mode::TABLE == m_parser_mode) { /* If we are in the TABLE mode, and parse_schema_ident() failed above, then we do not have a valid column identifier which is an error. */ if (!schema_ident) parse_error("Expected atomic expression"); /* Otherwise we complete parsing the column identifier and report it to the processor. */ parse_column_ident1(prc); return stored.release(); } /* Here we know that we are in DOCUMENT mode and we are expecting a document path. If parse_schema_ident() called above consumed some tokens, we check if they were not quoted identifiers. Such identifiers are allowed when referring to tables or columns but are invalid in a document path. */ if (Token::QWORD == types[0] || Token::QWORD == types[1]) parse_error("Expected atomic expression"); /* Now we treat the identifiers "A.B" parsed by parse_schema_ident() and stored as table/schema name in m_col_ref (if any), as an initial segment of a document field reference and complete parsing the whole document field. */ cdk::Doc_path_storage path; if (m_col_ref.table() && m_col_ref.table()->schema()) { parse_document_field( m_col_ref.table()->schema()->name(), m_col_ref.table()->name(), &path ); } else if (m_col_ref.table()) { parse_document_field(m_col_ref.table()->name(), &path); } else { parse_document_field(&path, true); } sprc->scalar()->ref(path); return stored.release(); } // ------------------------------------------------------------------------- Expression* Expr_parser_base::left_assoc_binary_op(const Op::Set &ops, Start lhs, Start rhs, Processor *prc) { // Store LHS of the expression smart_ptr<Expression> stored_lhs(parse(lhs, NULL)); const Token *t = consume_token(ops); if (!t) { /* There is no RHS, so LHS is the whole expression. If prc is NULL then we return already stored LHS. Otherwise we report stored LHS to the processor. */ if (!prc) return stored_lhs.release(); stored_lhs->process(*prc); return NULL; } Op::Type op = Op::get_binary(*t); /* If storing operator call expression (prc is NULL), use specialized Stored_op class that can re-use already stored LHS expression. */ smart_ptr<Stored_expr> stored; if (!prc) // Note: Stored_op takes ownership of the stored LHS expr. prc = stored.reset(new Stored_op(stored_lhs.release())); // pass lhs and rhs as operator arguments List_prc *aprc = safe_prc(prc)->scalar()->op(Op::name(op)); if (aprc) { aprc->list_begin(); // Report stored LHS as the 1st argument. stored_lhs->process_if(aprc->list_el()); // then parse rhs, passing it as 2nd argument delete parse(rhs, aprc->list_el()); aprc->list_end(); } return stored.release(); } Expression* Expr_parser_base::parse_mul(Processor *prc) { Op::Set ops; ops.insert(Op::MUL); ops.insert(Op::DIV); ops.insert(Op::MOD); return left_assoc_binary_op(ops, ATOMIC, MUL, prc); } Expression* Expr_parser_base::parse_add(Processor *prc) { Op::Set ops; ops.insert(Op::ADD); ops.insert(Op::SUB); return left_assoc_binary_op(ops, MUL, ADD, prc); } Expression* Expr_parser_base::parse_shift(Processor *prc) { Op::Set ops; ops.insert(Op::LSHIFT); ops.insert(Op::RSHIFT); return left_assoc_binary_op(ops, ADD, SHIFT, prc); } Expression* Expr_parser_base::parse_bit(Processor *prc) { if (consume_token(Op::BITNEG)) { smart_ptr<Stored_expr> stored; if (!prc) prc = stored.reset(new Stored_any()); Safe_prc<Processor::Scalar_prc> sprc(prc->scalar()); List_prc *argsp = NULL; argsp = sprc->op(Op::name(Op::BITNEG)); if (argsp) { argsp->list_begin(); delete parse(ATOMIC, argsp->list_el()); argsp->list_end(); return stored.release(); } return parse_bit(prc); } Op::Set ops; ops.insert(Op::BITAND); ops.insert(Op::BITOR); ops.insert(Op::BITXOR); return left_assoc_binary_op(ops, SHIFT, BIT, prc); } Expression* Expr_parser_base::parse_comp(Processor *prc) { Op::Set ops; ops.insert(Op::GE); ops.insert(Op::GT); ops.insert(Op::LE); ops.insert(Op::LT); ops.insert(Op::EQ); ops.insert(Op::NE); return left_assoc_binary_op(ops, BIT, COMP, prc); } Expression* Expr_parser_base::parse_and(Processor *prc) { return left_assoc_binary_op({ Op::AND }, ILRI, AND, prc); } Expression* Expr_parser_base::parse_or(Processor *prc) { return left_assoc_binary_op({ Op::OR }, AND, OR, prc); } // ------------------------------------------------------------------------- /** Expression Parser EBNF: note; No repetition, must be connected by logical operators ilriExpr ::= compExpr IS NOT? (NULL|TRUE|FALSE) | compExpr NOT? IN LPAREN argsList? RPAREN | compExpr NOT? "IN" compExpr // TODO: we don't know how to report ESCAPE on protocol level | compExpr NOT? LIKE compExpr //(ESCAPE compExpr)? | compExpr NOT? RLIKE compExpr //(ESCAPE compExpr)? | compExpr NOT? BETWEEN compExpr AND compExpr | compExpr NOT? REGEXP compExpr | compExpr */ Expression* Expr_parser_base::parse_ilri(Processor *prc) { // Store the first expression. smart_ptr<Expression> first(parse(COMP, NULL)); // Record negation, if present. bool neg = (NULL != consume_token(Op::NOT)); /* Look for the main operator. */ Op::Set next; next.insert(Op::IS); next.insert(Op::IN); next.insert(Op::LIKE); next.insert(Op::RLIKE); next.insert(Op::BETWEEN); next.insert(Op::REGEXP); next.insert(Op::SOUNDS_LIKE); next.insert(Op::OVERLAPS); const Token *t = consume_token(next); /* If we don't see any of the operators and there was no negation then we report the first expression as complete ilriExpr. */ if (!t) { if (neg) parse_error("Expected IN, (R)LIKE, BETWEEN, OVERLAPS or REGEXP after NOT"); // If prc is NULL return already stored expression. if (!prc) return first.release(); // Otherwise report stored expression to the processor. first->process(*prc); return NULL; } // We have an ilri expression with operator and 2 arguments. Op::Type op = Op::get_binary(*t); // Handle IS NOT case. if (neg && Op::IS == op) parse_error("Operator NOT before IS, should be IS NOT"); // Note: consume_token() replaces contents of *t... if (Op::IS == op && consume_token(Op::NOT)) neg = true; // Detect unsupported operators before handling parameters switch (op) { case Op::SOUNDS_LIKE: if (cur_token_type_is(Keyword::LIKE)) unsupported("Operator SOUNDS LIKE"); break; case Op::IS: if (neg) op = Op::IS_NOT; break; case Op::IN: if (!cur_token_type_is(Token::LPAREN)) { if (neg) op =Op::NOT_CONT_IN; else op = Op::CONT_IN; } else { if (neg) op =Op::NOT_IN; } break; case Op::LIKE: if (neg) op = Op::NOT_LIKE; break; case Op::RLIKE: if (neg) op = Op::NOT_RLIKE; break; case Op::BETWEEN: if (neg) op = Op::NOT_BETWEEN; break; case Op::REGEXP: if (neg) op = Op::NOT_REGEXP; break; case Op::OVERLAPS: if (neg) op = Op::NOT_OVERLAPS; break; default: break; } /* If prc is NULL and we are supposed to store parsed expression, use specialized Stored_ilri class that can re-use the already stored first part of the expression. */ List_prc *not_arg_prc = NULL; smart_ptr<Stored_ilri> stored; if (!prc) { prc = stored.reset(new Stored_ilri(first.release())); } // report the main operator Safe_prc<List_prc> aprc = safe_prc(prc)->scalar()->op(Op::name(op)); aprc->list_begin(); // 1st argument first->process_if(aprc->list_el()); // other arguments switch (op) { case Op::IS: case Op::IS_NOT: { t = consume_token(); if (t) { switch (Keyword::get(*t)) { case Keyword::L_TRUE: aprc->list_el()->scalar()->val()->yesno(true); break; case Keyword::L_FALSE: aprc->list_el()->scalar()->val()->yesno(false); break; case Keyword::L_NULL: aprc->list_el()->scalar()->val()->null(); break; default: t = NULL; // this indicates error } } if (!t) parse_error("expected TRUE, FALSE or NULL after IS"); break; } case Op::IN: case Op::CONT_IN: case Op::NOT_IN: case Op::NOT_CONT_IN: { if (consume_token(Token::LPAREN)) { // Note: true flag means that strings will be reported as blobs. parse_argslist(aprc, true); consume_token_throw( Token::RPAREN, "Expected ')' to close IN(... expression" ); } else { delete parse(COMP, aprc->list_el()); } break; } case Op::LIKE: case Op::NOT_LIKE: case Op::RLIKE: case Op::NOT_RLIKE: { delete parse(COMP, aprc->list_el()); if (cur_token_type_is(Keyword::ESCAPE)) { unsupported("ESCAPE clause for (R)LIKE operator"); } break; } case Op::REGEXP: case Op::NOT_REGEXP: delete parse(COMP, aprc->list_el()); break; case Op::OVERLAPS: case Op::NOT_OVERLAPS: delete parse(COMP, aprc->list_el()); break; case Op::BETWEEN: case Op::NOT_BETWEEN: delete parse(COMP, aprc->list_el()); consume_token_throw( Keyword::AND, "Expected AND in BETWEEN ... expression" ); delete parse(COMP, aprc->list_el()); break; default: assert(false); } // close argument list aprc->list_end(); if (not_arg_prc) not_arg_prc->list_end(); return stored.release(); } // ------------------------------------------------------------------------- /* Below we want to use Expr_parser_base with parser templates such as Doc_parser<> or List_parser<>. These templates assume that the base parser can be constructed with a constructor which accepts only 2 parameters defining the range of tokens to be parsed. But Expr_parser_base constructor also needs parser mode parameter and the flag which tells if strings should be reported as blobs. To fix this, we define Base_parser<> template parametrized with parser mode, which will construct required flavor of the parser. */ template <Parser_mode::value Mode, bool strings_as_blobs = false> struct Base_parser : public Expr_parser_base { Base_parser(It &first, const It &last) : Expr_parser_base(first, last, Mode, strings_as_blobs) {} }; template <Parser_mode::value Mode, bool strings_as_blobs> void parse_args(Processor::List_prc *prc, It &first, const It &last) { List_parser< Base_parser<Mode, strings_as_blobs> > args_parser(first, last); args_parser.process_if(prc); } template <bool strings_as_blobs> void parse_args(Parser_mode::value mode, Processor::List_prc *prc, It &first, const It &last) { if (Parser_mode::DOCUMENT == mode) parse_args<Parser_mode::DOCUMENT, strings_as_blobs>(prc, first, last); else parse_args<Parser_mode::TABLE, strings_as_blobs>(prc, first, last); } void Expr_parser_base::parse_argslist(Processor::List_prc *prc, bool strings_as_blobs) { /* argsList ::= expr (COMMA expr)* */ if (strings_as_blobs) parse_args<true>(m_parser_mode, prc, cur_pos(), end_pos()); else parse_args<false>(m_parser_mode, prc, cur_pos(), end_pos()); } void Expr_parser_base::parse_arr(Processor::List_prc *prc) { if (Parser_mode::DOCUMENT == m_parser_mode) { Arr_parser<Base_parser<Parser_mode::DOCUMENT>, Expression::Scalar::Processor> arr_parser(cur_pos(), end_pos()); arr_parser.process_if(prc); } else { Arr_parser<Base_parser<Parser_mode::TABLE>, Expression::Scalar::Processor> arr_parser(cur_pos(), end_pos()); arr_parser.process_if(prc); } } void Expr_parser_base::parse_doc(Processor::Doc_prc *prc) { if (Parser_mode::DOCUMENT == m_parser_mode) { Doc_parser<Base_parser<Parser_mode::DOCUMENT>, Expression::Scalar::Processor> doc_parser(cur_pos(), end_pos()); doc_parser.process_if(prc); } else { Doc_parser<Base_parser<Parser_mode::TABLE>, Expression::Scalar::Processor> doc_parser(cur_pos(), end_pos()); doc_parser.process_if(prc); } } void Order_parser::parse(Processor& prc) { It it = m_tokenizer.begin(); set_tokens(it, m_tokenizer.end()); if (!tokens_available()) parse_error("Expected sorting order specification"); Stored_any store_expr; Expr_parser_base parser(cur_pos(), end_pos(), m_mode); parser.process(store_expr); cdk::api::Sort_direction::value dir = cdk::api::Sort_direction::ASC; // get ASC/DESC token if available if (tokens_available()) { switch(Keyword::get(*peek_token())) { case Keyword::ASC: consume_token(); dir = cdk::api::Sort_direction::ASC; break; case Keyword::DESC: consume_token(); dir = cdk::api::Sort_direction::DESC; break; default: parse_error("Expected sorting direction ASC or DESC"); } } if (tokens_available()) parse_error("Unexpected characters after sorting order specification"); store_expr.process_if(prc.sort_key(dir)); } void Projection_parser::parse_tbl_mode(Projection_processor& prc) { It it = m_tokenizer.begin(); set_tokens(it, m_tokenizer.end()); if (!tokens_available()) parse_error("Expected projection specification"); Expr_parser_base parser(cur_pos(), end_pos(), m_mode); parser.process_if(prc.expr()); // get AS token if available if (tokens_available()) { if (!consume_token(Keyword::AS)) parse_error("Invalid characters in projection specification," " only AS <name> allowed after the projection expression"); if (!Token_base::cur_token_type_in({ Token::WORD, Token::QWORD })) parse_error("Expected identifier after AS"); prc.alias(consume_token()->get_text()); } if (tokens_available()) parse_error("Unexpected characters after projection specification"); } void Projection_parser::parse_doc_mode(Document_processor& prc) { It it = m_tokenizer.begin(); set_tokens(it, m_tokenizer.end()); if (!tokens_available()) parse_error("Expected projection specification"); /* note: passing m_toks.end() directly as constructor argument results in "incompatible iterators" exception when comparing iterators (at least on win, vs2010). problem with passing temporary object? */ Stored_any store_expr; Expr_parser_base parser(cur_pos(), end_pos(), m_mode); parser.process(store_expr); // AS is mandatory on Collections if (!consume_token(Keyword::AS)) parse_error("Expected AS in projection specification"); if (!Token_base::cur_token_type_in({Token::WORD,Token::QWORD})) parse_error("Expected identifier after AS"); const string &id = consume_token()->get_text(); if (tokens_available()) parse_error("Invalid characters after projection specification"); store_expr.process_if(prc.key_val(id)); }