1# Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License, version 2.0, as 5# published by the Free Software Foundation. 6# 7# This program is also distributed with certain software (including 8# but not limited to OpenSSL) that is licensed under separate terms, 9# as designated in a particular file or component or in included license 10# documentation. The authors of MySQL hereby grant you an 11# additional permission to link the program and your derivative works 12# with the separately licensed software that they have included with 13# MySQL. 14# 15# Without limiting anything contained in the foregoing, this file, 16# which is part of MySQL Connector/Python, is also subject to the 17# Universal FOSS Exception, version 1.0, a copy of which can be found at 18# http://oss.oracle.com/licenses/universal-foss-exception. 19# 20# This program is distributed in the hope that it will be useful, but 21# WITHOUT ANY WARRANTY; without even the implied warranty of 22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23# See the GNU General Public License, version 2.0, for more details. 24# 25# You should have received a copy of the GNU General Public License 26# along with this program; if not, write to the Free Software Foundation, Inc., 27# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 28 29"""Expression Parser.""" 30 31from .helpers import BYTE_TYPES, get_item_or_attr 32from .dbdoc import DbDoc 33from .protobuf import Message, mysqlxpb_enum 34 35 36# pylint: disable=C0103,C0111 37class TokenType(object): 38 NOT = 1 39 AND = 2 40 OR = 3 41 XOR = 4 42 IS = 5 43 LPAREN = 6 44 RPAREN = 7 45 LSQBRACKET = 8 46 RSQBRACKET = 9 47 BETWEEN = 10 48 TRUE = 11 49 NULL = 12 50 FALSE = 13 51 IN = 14 52 LIKE = 15 53 INTERVAL = 16 54 REGEXP = 17 55 ESCAPE = 18 56 IDENT = 19 57 LSTRING = 20 58 LNUM = 21 59 DOT = 22 60 DOLLAR = 23 61 COMMA = 24 62 EQ = 25 63 NE = 26 64 GT = 27 65 GE = 28 66 LT = 29 67 LE = 30 68 BITAND = 31 69 BITOR = 32 70 BITXOR = 33 71 LSHIFT = 34 72 RSHIFT = 35 73 PLUS = 36 74 MINUS = 37 75 MUL = 38 76 DIV = 39 77 HEX = 40 78 BIN = 41 79 NEG = 42 80 BANG = 43 81 MICROSECOND = 44 82 SECOND = 45 83 MINUTE = 46 84 HOUR = 47 85 DAY = 48 86 WEEK = 49 87 MONTH = 50 88 QUARTER = 51 89 YEAR = 52 90 EROTEME = 53 91 DOUBLESTAR = 54 92 MOD = 55 93 COLON = 56 94 OROR = 57 95 ANDAND = 58 96 LCURLY = 59 97 RCURLY = 60 98 CAST = 61 99 DOTSTAR = 62 100 ORDERBY_ASC = 63 101 ORDERBY_DESC = 64 102 AS = 65 103 ARROW = 66 104 QUOTE = 67 105 BINARY = 68 106 DATETIME = 69 107 TIME = 70 108 CHAR = 71 109 DATE = 72 110 DECIMAL = 73 111 SIGNED = 74 112 INTEGER = 75 113 UNSIGNED = 76 114 JSON = 77 115 SECOND_MICROSECOND = 78 116 MINUTE_MICROSECOND = 79 117 MINUTE_SECOND = 80 118 HOUR_MICROSECOND = 81 119 HOUR_SECOND = 82 120 HOUR_MINUTE = 83 121 DAY_MICROSECOND = 84 122 DAY_SECOND = 85 123 DAY_MINUTE = 86 124 DAY_HOUR = 87 125 YEAR_MONTH = 88 126 OVERLAPS = 89 127# pylint: enable=C0103 128 129_INTERVAL_UNITS = set([ 130 TokenType.MICROSECOND, 131 TokenType.SECOND, 132 TokenType.MINUTE, 133 TokenType.HOUR, 134 TokenType.DAY, 135 TokenType.WEEK, 136 TokenType.MONTH, 137 TokenType.QUARTER, 138 TokenType.YEAR, 139 TokenType.SECOND_MICROSECOND, 140 TokenType.MINUTE_MICROSECOND, 141 TokenType.MINUTE_SECOND, 142 TokenType.HOUR_MICROSECOND, 143 TokenType.HOUR_SECOND, 144 TokenType.HOUR_MINUTE, 145 TokenType.DAY_MICROSECOND, 146 TokenType.DAY_SECOND, 147 TokenType.DAY_MINUTE, 148 TokenType.DAY_HOUR, 149 TokenType.YEAR_MONTH]) 150 151# map of reserved word to token type 152_RESERVED_WORDS = { 153 "and": TokenType.AND, 154 "or": TokenType.OR, 155 "xor": TokenType.XOR, 156 "is": TokenType.IS, 157 "not": TokenType.NOT, 158 "like": TokenType.LIKE, 159 "in": TokenType.IN, 160 "overlaps": TokenType.OVERLAPS, 161 "regexp": TokenType.REGEXP, 162 "between": TokenType.BETWEEN, 163 "interval": TokenType.INTERVAL, 164 "escape": TokenType.ESCAPE, 165 "cast": TokenType.CAST, 166 "div": TokenType.DIV, 167 "hex": TokenType.HEX, 168 "bin": TokenType.BIN, 169 "true": TokenType.TRUE, 170 "false": TokenType.FALSE, 171 "null": TokenType.NULL, 172 "second": TokenType.SECOND, 173 "minute": TokenType.MINUTE, 174 "hour": TokenType.HOUR, 175 "day": TokenType.DAY, 176 "week": TokenType.WEEK, 177 "month": TokenType.MONTH, 178 "quarter": TokenType.QUARTER, 179 "year": TokenType.YEAR, 180 "microsecond": TokenType.MICROSECOND, 181 "asc": TokenType.ORDERBY_ASC, 182 "desc": TokenType.ORDERBY_DESC, 183 "as": TokenType.AS, 184 "binary": TokenType.BINARY, 185 "datetime": TokenType.DATETIME, 186 "time": TokenType.TIME, 187 "char": TokenType.CHAR, 188 "date": TokenType.DATE, 189 "decimal": TokenType.DECIMAL, 190 "signed": TokenType.SIGNED, 191 "unsigned": TokenType.UNSIGNED, 192 "integer": TokenType.INTEGER, 193 "json": TokenType.JSON, 194 "second_microsecond": TokenType.SECOND_MICROSECOND, 195 "minute_microsecond": TokenType.MINUTE_MICROSECOND, 196 "minute_second": TokenType.MINUTE_SECOND, 197 "hour_microsecond": TokenType.HOUR_MICROSECOND, 198 "hour_second": TokenType.HOUR_SECOND, 199 "hour_minute": TokenType.HOUR_MINUTE, 200 "day_microsecond": TokenType.DAY_MICROSECOND, 201 "day_second": TokenType.DAY_SECOND, 202 "day_minute": TokenType.DAY_MINUTE, 203 "day_hour": TokenType.DAY_HOUR, 204 "year_month": TokenType.YEAR_MONTH 205} 206 207_SQL_FUNTION_RESERVED_WORDS_COLLISION = { 208 "binary": TokenType.BINARY, 209 "cast": TokenType.CAST, 210 "char": TokenType.CHAR, 211 "date": TokenType.DATE, 212 "decimal": TokenType.DECIMAL, 213 "signed": TokenType.SIGNED, 214 "time": TokenType.TIME, 215 "unsigned": TokenType.UNSIGNED, 216} 217 218_OPERATORS = { 219 "=": "==", 220 "==": "==", 221 "and": "&&", 222 "div": "div", 223 "||": "||", 224 "or": "||", 225 "not": "not", 226 "xor": "xor", 227 "^": "^", 228 "is": "is", 229 "between": "between", 230 "in": "in", 231 "like": "like", 232 "!=": "!=", 233 "<>": "!=", 234 ">": ">", 235 ">=": ">=", 236 "<": "<", 237 "<=": "<=", 238 "&": "&", 239 "&&": "&&", 240 "|": "|", 241 "<<": "<<", 242 ">>": ">>", 243 "+": "+", 244 "-": "-", 245 "*": "*", 246 "/": "/", 247 "~": "~", 248 "%": "%", 249 "cast": "cast", 250 "cont_in": "cont_in", 251 "overlaps": "overlaps" 252} 253 254_UNARY_OPERATORS = { 255 "+": "sign_plus", 256 "-": "sign_minus", 257 "~": "~", 258 "not": "not", 259 "!": "!" 260} 261 262_NEGATION = { 263 "is": "is_not", 264 "between": "not_between", 265 "regexp": "not_regexp", 266 "like": "not_like", 267 "in": "not_in", 268 "cont_in": "not_cont_in", 269 "overlaps": "not_overlaps", 270} 271 272 273class Token(object): 274 def __init__(self, token_type, value, length=1): 275 self.token_type = token_type 276 self.value = value 277 self.length = length 278 279 def __repr__(self): 280 return self.__str__() 281 282 def __str__(self): 283 if self.token_type == TokenType.IDENT or \ 284 self.token_type == TokenType.LNUM or \ 285 self.token_type == TokenType.LSTRING: 286 return "{0}({1})".format(self.token_type, self.value) 287 return "{0}".format(self.token_type) 288 289 290# static protobuf helper functions 291 292def build_expr(value): 293 msg = Message("Mysqlx.Expr.Expr") 294 if isinstance(value, (Message)): 295 return value 296 elif isinstance(value, (ExprParser)): 297 return value.expr(reparse=True) 298 elif isinstance(value, (dict, DbDoc)): 299 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT") 300 msg["object"] = build_object(value).get_message() 301 elif isinstance(value, (list, tuple)): 302 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY") 303 msg["array"] = build_array(value).get_message() 304 else: 305 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL") 306 msg["literal"] = build_scalar(value).get_message() 307 return msg 308 309 310def build_scalar(value): 311 if isinstance(value, str): 312 return build_string_scalar(value) 313 elif isinstance(value, BYTE_TYPES): 314 return build_bytes_scalar(value) 315 elif isinstance(value, bool): 316 return build_bool_scalar(value) 317 elif isinstance(value, int): 318 return build_int_scalar(value) 319 elif isinstance(value, float): 320 return build_double_scalar(value) 321 elif value is None: 322 return build_null_scalar() 323 raise ValueError("Unsupported data type: {0}.".format(type(value))) 324 325 326def build_object(obj): 327 if isinstance(obj, DbDoc): 328 return build_object(obj.__dict__) 329 330 msg = Message("Mysqlx.Expr.Object") 331 for key, value in obj.items(): 332 pair = Message("Mysqlx.Expr.Object.ObjectField") 333 pair["key"] = key.encode() if isinstance(key, str) else key 334 pair["value"] = build_expr(value).get_message() 335 msg["fld"].extend([pair.get_message()]) 336 return msg 337 338 339def build_array(array): 340 msg = Message("Mysqlx.Expr.Array") 341 msg["value"].extend([build_expr(value).get_message() for value in array]) 342 return msg 343 344 345def build_null_scalar(): 346 msg = Message("Mysqlx.Datatypes.Scalar") 347 msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_NULL") 348 return msg 349 350 351def build_double_scalar(value): 352 msg = Message("Mysqlx.Datatypes.Scalar") 353 msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_DOUBLE") 354 msg["v_double"] = value 355 return msg 356 357 358def build_int_scalar(value): 359 msg = Message("Mysqlx.Datatypes.Scalar") 360 msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_SINT") 361 msg["v_signed_int"] = value 362 return msg 363 364def build_unsigned_int_scalar(value): 365 msg = Message("Mysqlx.Datatypes.Scalar") 366 msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_UINT") 367 msg["v_unsigned_int"] = value 368 return msg 369 370def build_string_scalar(value): 371 if isinstance(value, str): 372 value = bytes(bytearray(value, "utf-8")) 373 msg = Message("Mysqlx.Datatypes.Scalar") 374 msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_STRING") 375 msg["v_string"] = Message("Mysqlx.Datatypes.Scalar.String", value=value) 376 return msg 377 378 379def build_bool_scalar(value): 380 msg = Message("Mysqlx.Datatypes.Scalar") 381 msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_BOOL") 382 msg["v_bool"] = value 383 return msg 384 385 386def build_bytes_scalar(value): 387 msg = Message("Mysqlx.Datatypes.Scalar") 388 msg["type"] = mysqlxpb_enum("Mysqlx.Datatypes.Scalar.Type.V_OCTETS") 389 msg["v_octets"] = Message("Mysqlx.Datatypes.Scalar.Octets", value=value) 390 return msg 391 392 393def build_literal_expr(value): 394 msg = Message("Mysqlx.Expr.Expr") 395 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.LITERAL") 396 msg["literal"] = value 397 return msg 398 399 400def build_unary_op(name, param): 401 operator = Message("Mysqlx.Expr.Operator") 402 operator["name"] = _UNARY_OPERATORS[name] 403 operator["param"] = [param.get_message()] 404 msg = Message("Mysqlx.Expr.Expr") 405 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR") 406 msg["operator"] = operator.get_message() 407 return msg 408 409 410def escape_literal(string): 411 return string.replace('"', '""') 412 413 414class ExprParser(object): 415 def __init__(self, string, allow_relational=True): 416 self.string = string 417 self.tokens = [] 418 self.path_name_queue = [] 419 self.pos = 0 420 self._allow_relational_columns = allow_relational 421 self.placeholder_name_to_position = {} 422 self.positional_placeholder_count = 0 423 self.clean_expression() 424 self.lex() 425 426 def __str__(self): 427 return "<mysqlx.ExprParser '{}'>".format(self.string) 428 429 def clean_expression(self): 430 """Removes the keywords that does not form part of the expression. 431 432 Removes the keywords "SELECT" and "WHERE" that does not form part of 433 the expression itself. 434 """ 435 if not isinstance(self.string, str): 436 self.string = repr(self.string) 437 self.string = self.string.strip(" ") 438 if len(self.string) > 1 and self.string[-1] == ';': 439 self.string = self.string[:-1] 440 if "SELECT" in self.string[:6].upper(): 441 self.string = self.string[6:] 442 if "WHERE" in self.string[:5].upper(): 443 self.string = self.string[5:] 444 445 # convencience checker for lexer 446 def next_char_is(self, key, char): 447 return key + 1 < len(self.string) and self.string[key + 1] == char 448 449 def lex_number(self, pos): 450 # numeric literal 451 start = pos 452 found_dot = False 453 while pos < len(self.string) and (self.string[pos].isdigit() or 454 self.string[pos] == "."): 455 if self.string[pos] == ".": 456 if found_dot is True: 457 raise ValueError("Invalid number. Found multiple '.'") 458 found_dot = True 459 # technically we allow more than one "." and let float()'s parsing 460 # complain later 461 pos += 1 462 val = self.string[start:pos] 463 return Token(TokenType.LNUM, val, len(val)) 464 465 def lex_alpha(self, i, allow_space=False): 466 start = i 467 while i < len(self.string) and \ 468 (self.string[i].isalnum() or self.string[i] == "_" or 469 (self.string[i].isspace() and allow_space)): 470 i += 1 471 472 val = self.string[start:i] 473 try: 474 if i < len(self.string) and self.string[i] == '(' and \ 475 val.lower() not in _SQL_FUNTION_RESERVED_WORDS_COLLISION: 476 token = Token(TokenType.IDENT, val, len(val)) 477 else: 478 token = Token(_RESERVED_WORDS[val.lower()], val.lower(), len(val)) 479 except KeyError: 480 token = Token(TokenType.IDENT, val, len(val)) 481 return token 482 483 def lex_quoted_token(self, key): 484 quote_char = self.string[key] 485 val = "" 486 key += 1 487 start = key 488 while key < len(self.string): 489 char = self.string[key] 490 if char == quote_char and key + 1 < len(self.string) and \ 491 self.string[key + 1] != quote_char: 492 # break if we have a quote char that's not double 493 break 494 elif char == quote_char or char == "\\": 495 # this quote char has to be doubled 496 if key + 1 >= len(self.string): 497 break 498 key += 1 499 val += self.string[key] 500 else: 501 val += char 502 key += 1 503 if key >= len(self.string) or self.string[key] != quote_char: 504 raise ValueError("Unterminated quoted string starting at {0}" 505 "".format(start)) 506 if quote_char == "`": 507 return Token(TokenType.IDENT, val, len(val) + 2) 508 return Token(TokenType.LSTRING, val, len(val) + 2) 509 510 def lex(self): 511 i = 0 512 arrow_last = False 513 inside_arrow = False 514 while i < len(self.string): 515 char = self.string[i] 516 if char.isspace(): 517 i += 1 518 continue 519 elif char.isdigit(): 520 token = self.lex_number(i) 521 elif char.isalpha() or char == "_": 522 token = self.lex_alpha(i, inside_arrow) 523 elif char == "?": 524 token = Token(TokenType.EROTEME, char) 525 elif char == ":": 526 token = Token(TokenType.COLON, char) 527 elif char == "{": 528 token = Token(TokenType.LCURLY, char) 529 elif char == "}": 530 token = Token(TokenType.RCURLY, char) 531 elif char == "+": 532 token = Token(TokenType.PLUS, char) 533 elif char == "-": 534 if self.next_char_is(i, ">") and not arrow_last: 535 token = Token(TokenType.ARROW, "->", 2) 536 arrow_last = True 537 else: 538 token = Token(TokenType.MINUS, char) 539 elif char == "*": 540 if self.next_char_is(i, "*"): 541 token = Token(TokenType.DOUBLESTAR, "**", 2) 542 else: 543 token = Token(TokenType.MUL, char) 544 elif char == "/": 545 token = Token(TokenType.DIV, char) 546 elif char == "$": 547 token = Token(TokenType.DOLLAR, char) 548 elif char == "%": 549 token = Token(TokenType.MOD, char) 550 elif char == "=": 551 if self.next_char_is(i, "="): 552 token = Token(TokenType.EQ, "==", 2) 553 else: 554 token = Token(TokenType.EQ, "==", 1) 555 elif char == "&": 556 if self.next_char_is(i, "&"): 557 token = Token(TokenType.ANDAND, "&&", 2) 558 else: 559 token = Token(TokenType.BITAND, char) 560 elif char == "^": 561 token = Token(TokenType.BITXOR, char) 562 elif char == "|": 563 if self.next_char_is(i, "|"): 564 token = Token(TokenType.OROR, "||", 2) 565 else: 566 token = Token(TokenType.BITOR, char) 567 elif char == "(": 568 token = Token(TokenType.LPAREN, char) 569 elif char == ")": 570 token = Token(TokenType.RPAREN, char) 571 elif char == "[": 572 token = Token(TokenType.LSQBRACKET, char) 573 elif char == "]": 574 token = Token(TokenType.RSQBRACKET, char) 575 elif char == "~": 576 token = Token(TokenType.NEG, char) 577 elif char == ",": 578 token = Token(TokenType.COMMA, char) 579 elif char == "!": 580 if self.next_char_is(i, "="): 581 token = Token(TokenType.NE, "!=", 2) 582 else: 583 token = Token(TokenType.BANG, char) 584 elif char == "<": 585 if self.next_char_is(i, ">"): 586 token = Token(TokenType.NE, "<>", 2) 587 elif self.next_char_is(i, "<"): 588 token = Token(TokenType.LSHIFT, "<<", 2) 589 elif self.next_char_is(i, "="): 590 token = Token(TokenType.LE, "<=", 2) 591 else: 592 token = Token(TokenType.LT, char) 593 elif char == ">": 594 if self.next_char_is(i, ">"): 595 token = Token(TokenType.RSHIFT, ">>", 2) 596 elif self.next_char_is(i, "="): 597 token = Token(TokenType.GE, ">=", 2) 598 else: 599 token = Token(TokenType.GT, char) 600 elif char == ".": 601 if self.next_char_is(i, "*"): 602 token = Token(TokenType.DOTSTAR, ".*", 2) 603 elif i + 1 < len(self.string) and self.string[i + 1].isdigit(): 604 token = self.lex_number(i) 605 else: 606 token = Token(TokenType.DOT, char) 607 elif (char == "'" or char == '"') and arrow_last: 608 token = Token(TokenType.QUOTE, char) 609 if not inside_arrow: 610 inside_arrow = True 611 else: 612 arrow_last = False 613 inside_arrow = False 614 elif char == '"' or char == "'" or char == "`": 615 token = self.lex_quoted_token(i) 616 else: 617 raise ValueError("Unknown character at {0}".format(i)) 618 self.tokens.append(token) 619 i += token.length 620 621 def assert_cur_token(self, token_type): 622 if self.pos >= len(self.tokens): 623 raise ValueError("Expected token type {0} at pos {1} but no " 624 "tokens left".format(token_type, self.pos)) 625 if self.tokens[self.pos].token_type != token_type: 626 raise ValueError("Expected token type {0} at pos {1} but found " 627 "type {2}, on tokens {3}" 628 "".format(token_type, self.pos, 629 self.tokens[self.pos], self.tokens)) 630 631 def cur_token_type_is(self, token_type): 632 return self.pos_token_type_is(self.pos, token_type) 633 634 def cur_token_type_in(self, *types): 635 return self.pos < len(self.tokens) and \ 636 self.tokens[self.pos].token_type in types 637 638 def next_token_type_is(self, token_type): 639 return self.pos_token_type_is(self.pos + 1, token_type) 640 641 def next_token_type_in(self, *types): 642 return self.pos < len(self.tokens) and \ 643 self.tokens[self.pos + 1].token_type in types 644 645 def pos_token_type_is(self, pos, token_type): 646 return pos < len(self.tokens) and \ 647 self.tokens[pos].token_type == token_type 648 649 def consume_token(self, token_type): 650 self.assert_cur_token(token_type) 651 value = self.tokens[self.pos].value 652 self.pos += 1 653 return value 654 655 def paren_expr_list(self): 656 """Parse a paren-bounded expression list for function arguments or IN 657 list and return a list of Expr objects. 658 """ 659 exprs = [] 660 path_name_added = False 661 self.consume_token(TokenType.LPAREN) 662 if not self.cur_token_type_is(TokenType.RPAREN): 663 msg_expr = self._expr().get_message() 664 if hasattr(msg_expr, "identifier") and msg_expr.identifier.name: 665 self.path_name_queue.insert(0, msg_expr.identifier.name) 666 path_name_added = True 667 elif not hasattr(msg_expr, "identifier") and \ 668 "identifier" in msg_expr and "name" in msg_expr["identifier"]: 669 self.path_name_queue.insert(0, msg_expr["identifier"]["name"]) 670 path_name_added = True 671 exprs.append(msg_expr) 672 while self.cur_token_type_is(TokenType.COMMA): 673 self.pos += 1 674 exprs.append(self._expr().get_message()) 675 self.consume_token(TokenType.RPAREN) 676 if path_name_added: 677 self.path_name_queue.pop() 678 return exprs 679 680 def identifier(self): 681 self.assert_cur_token(TokenType.IDENT) 682 ident = Message("Mysqlx.Expr.Identifier") 683 if self.next_token_type_is(TokenType.DOT): 684 ident["schema_name"] = self.consume_token(TokenType.IDENT) 685 self.consume_token(TokenType.DOT) 686 ident["name"] = self.tokens[self.pos].value 687 self.pos += 1 688 return ident 689 690 def function_call(self): 691 function_call = Message("Mysqlx.Expr.FunctionCall") 692 function_call["name"] = self.identifier() 693 function_call["param"] = self.paren_expr_list() 694 msg_expr = Message("Mysqlx.Expr.Expr") 695 msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.FUNC_CALL") 696 msg_expr["function_call"] = function_call.get_message() 697 return msg_expr 698 699 def docpath_member(self): 700 self.consume_token(TokenType.DOT) 701 token = self.tokens[self.pos] 702 703 if token.token_type == TokenType.IDENT: 704 if token.value.startswith('`') and token.value.endswith('`'): 705 raise ValueError("{0} is not a valid JSON/ECMAScript " 706 "identifier".format(token.value)) 707 self.consume_token(TokenType.IDENT) 708 member_name = token.value 709 elif token.token_type == TokenType.LSTRING: 710 self.consume_token(TokenType.LSTRING) 711 member_name = token.value 712 else: 713 raise ValueError("Expected token type IDENT or LSTRING in JSON " 714 "path at token pos {0}".format(self.pos)) 715 doc_path_item = Message("Mysqlx.Expr.DocumentPathItem") 716 doc_path_item["type"] = mysqlxpb_enum( 717 "Mysqlx.Expr.DocumentPathItem.Type.MEMBER") 718 doc_path_item["value"] = member_name 719 return doc_path_item 720 721 def docpath_array_loc(self): 722 self.consume_token(TokenType.LSQBRACKET) 723 if self.cur_token_type_is(TokenType.MUL): 724 self.consume_token(TokenType.MUL) 725 self.consume_token(TokenType.RSQBRACKET) 726 doc_path_item = Message("Mysqlx.Expr.DocumentPathItem") 727 doc_path_item["type"] = mysqlxpb_enum( 728 "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX_ASTERISK") 729 return doc_path_item 730 elif self.cur_token_type_is(TokenType.LNUM): 731 value = int(self.consume_token(TokenType.LNUM)) 732 if value < 0: 733 raise IndexError("Array index cannot be negative at {0}" 734 "".format(self.pos)) 735 self.consume_token(TokenType.RSQBRACKET) 736 doc_path_item = Message("Mysqlx.Expr.DocumentPathItem") 737 doc_path_item["type"] = mysqlxpb_enum( 738 "Mysqlx.Expr.DocumentPathItem.Type.ARRAY_INDEX") 739 doc_path_item["index"] = value 740 return doc_path_item 741 else: 742 raise ValueError("Exception token type MUL or LNUM in JSON " 743 "path array index at token pos {0}" 744 "".format(self.pos)) 745 746 def document_field(self): 747 col_id = Message("Mysqlx.Expr.ColumnIdentifier") 748 if self.cur_token_type_is(TokenType.IDENT): 749 doc_path_item = Message("Mysqlx.Expr.DocumentPathItem") 750 doc_path_item["type"] = mysqlxpb_enum( 751 "Mysqlx.Expr.DocumentPathItem.Type.MEMBER") 752 doc_path_item["value"] = self.consume_token(TokenType.IDENT) 753 col_id["document_path"].extend([doc_path_item.get_message()]) 754 col_id["document_path"].extend(self.document_path()) 755 if self.path_name_queue: 756 col_id["name"] = self.path_name_queue[0] 757 expr = Message("Mysqlx.Expr.Expr") 758 expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT") 759 expr["identifier"] = col_id 760 return expr 761 762 def document_path(self): 763 """Parse a JSON-style document path, like WL#7909, but prefix by @. 764 instead of $. We parse this as a string because the protocol doesn't 765 support it. (yet) 766 """ 767 doc_path = [] 768 while True: 769 if self.cur_token_type_is(TokenType.DOT): 770 doc_path.append(self.docpath_member().get_message()) 771 elif self.cur_token_type_is(TokenType.DOTSTAR): 772 self.consume_token(TokenType.DOTSTAR) 773 doc_path_item = Message("Mysqlx.Expr.DocumentPathItem") 774 doc_path_item["type"] = mysqlxpb_enum( 775 "Mysqlx.Expr.DocumentPathItem.Type.MEMBER_ASTERISK") 776 doc_path.append(doc_path_item.get_message()) 777 elif self.cur_token_type_is(TokenType.LSQBRACKET): 778 doc_path.append(self.docpath_array_loc().get_message()) 779 elif self.cur_token_type_is(TokenType.DOUBLESTAR): 780 self.consume_token(TokenType.DOUBLESTAR) 781 doc_path_item = Message("Mysqlx.Expr.DocumentPathItem") 782 doc_path_item["type"] = mysqlxpb_enum( 783 "Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK") 784 doc_path.append(doc_path_item.get_message()) 785 else: 786 break 787 items = len(doc_path) 788 if items > 0 and get_item_or_attr(doc_path[items - 1], "type") == \ 789 mysqlxpb_enum("Mysqlx.Expr.DocumentPathItem.Type.DOUBLE_ASTERISK"): 790 raise ValueError("JSON path may not end in '**' at {0}" 791 "".format(self.pos)) 792 return doc_path 793 794 def column_identifier(self): 795 parts = [] 796 parts.append(self.consume_token(TokenType.IDENT)) 797 while self.cur_token_type_is(TokenType.DOT): 798 self.consume_token(TokenType.DOT) 799 parts.append(self.consume_token(TokenType.IDENT)) 800 if len(parts) > 3: 801 raise ValueError("Too many parts to identifier at {0}" 802 "".format(self.pos)) 803 parts.reverse() 804 col_id = Message("Mysqlx.Expr.ColumnIdentifier") 805 # clever way to apply them to the struct 806 for i in range(0, len(parts)): 807 if i == 0: 808 col_id["name"] = parts[0] 809 elif i == 1: 810 col_id["table_name"] = parts[1] 811 elif i == 2: 812 col_id["schema_name"] = parts[2] 813 814 is_doc = False 815 if self.cur_token_type_is(TokenType.DOLLAR): 816 is_doc = True 817 self.consume_token(TokenType.DOLLAR) 818 col_id["document_path"] = self.document_path() 819 elif self.cur_token_type_is(TokenType.ARROW): 820 is_doc = True 821 self.consume_token(TokenType.ARROW) 822 is_quoted = False 823 if self.cur_token_type_is(TokenType.QUOTE): 824 is_quoted = True 825 self.consume_token(TokenType.QUOTE) 826 self.consume_token(TokenType.DOLLAR) 827 col_id["document_path"] = self.document_path() 828 if is_quoted: 829 self.consume_token(TokenType.QUOTE) 830 831 if is_doc and len(col_id["document_path"]) == 0: 832 doc_path_item = Message("Mysqlx.Expr.DocumentPathItem") 833 doc_path_item["type"] = mysqlxpb_enum( 834 "Mysqlx.Expr.DocumentPathItem.Type.MEMBER") 835 doc_path_item["value"] = "" 836 col_id["document_path"].extend([doc_path_item.get_message()]) 837 838 msg_expr = Message("Mysqlx.Expr.Expr") 839 msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.IDENT") 840 msg_expr["identifier"] = col_id 841 return msg_expr 842 843 def next_token(self): 844 if self.pos >= len(self.tokens): 845 raise ValueError("Unexpected end of token stream") 846 token = self.tokens[self.pos] 847 self.pos += 1 848 return token 849 850 def expect_token(self, token_type): 851 token = self.next_token() 852 if token.token_type != token_type: 853 raise ValueError("Expected token type {0}".format(token_type)) 854 855 def peek_token(self): 856 return self.tokens[self.pos] 857 858 def consume_any_token(self): 859 value = self.tokens[self.pos].value 860 self.pos += 1 861 return value 862 863 def parse_json_array(self): 864 """ 865 jsonArray ::= "[" [ expr ("," expr)* ] "]" 866 """ 867 msg = Message("Mysqlx.Expr.Array") 868 while self.pos < len(self.tokens) and \ 869 not self.cur_token_type_is(TokenType.RSQBRACKET): 870 msg["value"].extend([self._expr().get_message()]) 871 if not self.cur_token_type_is(TokenType.COMMA): 872 break 873 self.consume_token(TokenType.COMMA) 874 self.consume_token(TokenType.RSQBRACKET) 875 876 expr = Message("Mysqlx.Expr.Expr") 877 expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.ARRAY") 878 expr["array"] = msg.get_message() 879 return expr 880 881 def parse_json_doc(self): 882 """ 883 jsonDoc ::= "{" [jsonKeyValue ("," jsonKeyValue)*] "}" 884 jsonKeyValue ::= STRING_DQ ":" expr 885 """ 886 msg = Message("Mysqlx.Expr.Object") 887 while self.pos < len(self.tokens) and \ 888 not self.cur_token_type_is(TokenType.RCURLY): 889 item = Message("Mysqlx.Expr.Object.ObjectField") 890 item["key"] = self.consume_token(TokenType.LSTRING) 891 self.consume_token(TokenType.COLON) 892 item["value"] = self._expr().get_message() 893 msg["fld"].extend([item.get_message()]) 894 if not self.cur_token_type_is(TokenType.COMMA): 895 break 896 self.consume_token(TokenType.COMMA) 897 self.consume_token(TokenType.RCURLY) 898 899 expr = Message("Mysqlx.Expr.Expr") 900 expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OBJECT") 901 expr["object"] = msg.get_message() 902 return expr 903 904 def parse_place_holder(self, token): 905 place_holder_name = "" 906 if self.cur_token_type_is(TokenType.LNUM): 907 place_holder_name = self.consume_token(TokenType.LNUM) 908 elif self.cur_token_type_is(TokenType.IDENT): 909 place_holder_name = self.consume_token(TokenType.IDENT) 910 elif token.token_type == TokenType.EROTEME: 911 place_holder_name = str(self.positional_placeholder_count) 912 else: 913 raise ValueError("Invalid placeholder name at token pos {0}" 914 "".format(self.pos)) 915 916 msg_expr = Message("Mysqlx.Expr.Expr") 917 msg_expr["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.PLACEHOLDER") 918 if place_holder_name in self.placeholder_name_to_position: 919 msg_expr["position"] = \ 920 self.placeholder_name_to_position[place_holder_name] 921 else: 922 msg_expr["position"] = self.positional_placeholder_count 923 self.placeholder_name_to_position[place_holder_name] = \ 924 self.positional_placeholder_count 925 self.positional_placeholder_count += 1 926 return msg_expr 927 928 def cast(self): 929 """ cast ::= CAST LPAREN expr AS cast_data_type RPAREN 930 """ 931 operator = Message("Mysqlx.Expr.Operator", name="cast") 932 self.consume_token(TokenType.LPAREN) 933 operator["param"].extend([self._expr().get_message()]) 934 self.consume_token(TokenType.AS) 935 936 type_scalar = build_bytes_scalar(str.encode(self.cast_data_type())) 937 operator["param"].extend( 938 [build_literal_expr(type_scalar).get_message()]) 939 self.consume_token(TokenType.RPAREN) 940 msg = Message("Mysqlx.Expr.Expr", operator=operator.get_message()) 941 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR") 942 return msg 943 944 def cast_data_type(self): 945 """ cast_data_type ::= ( BINARY dimension? ) | 946 ( CHAR dimension? ) | 947 ( DATE ) | 948 ( DATETIME dimension? ) | 949 ( TIME dimension? ) | 950 ( DECIMAL dimension? ) | 951 ( SIGNED INTEGER? ) | 952 ( UNSIGNED INTEGER? ) | 953 JSON 954 """ 955 token = self.next_token() 956 if token.token_type in (TokenType.BINARY, TokenType.CHAR, 957 TokenType.DATETIME, TokenType.TIME,): 958 dimension = self.cast_data_type_dimension() 959 return "{0}{1}".format(token.value, dimension) \ 960 if dimension else token.value 961 elif token.token_type is TokenType.DECIMAL: 962 dimension = self.cast_data_type_dimension(True) 963 return "{0}{1}".format(token.value, dimension) \ 964 if dimension else token.value 965 elif token.token_type in (TokenType.SIGNED, TokenType.UNSIGNED,): 966 if self.cur_token_type_is(TokenType.INTEGER): 967 self.consume_token(TokenType.INTEGER) 968 return token.value 969 elif token.token_type in (TokenType.INTEGER, TokenType.JSON, 970 TokenType.DATE,): 971 return token.value 972 973 raise ValueError("Unknown token type {0} at position {1} ({2})" 974 "".format(token.token_type, self.pos, token.value)) 975 976 def cast_data_type_dimension(self, decimal=False): 977 """ dimension ::= LPAREN LNUM (, LNUM)? RPAREN 978 """ 979 if not self.cur_token_type_is(TokenType.LPAREN): 980 return None 981 982 dimension = [] 983 self.consume_token(TokenType.LPAREN) 984 dimension.append(self.consume_token(TokenType.LNUM)) 985 if decimal and self.cur_token_type_is(TokenType.COMMA): 986 self.consume_token(TokenType.COMMA) 987 dimension.append(self.consume_token(TokenType.LNUM)) 988 self.consume_token(TokenType.RPAREN) 989 990 return "({0})".format(dimension[0]) if len(dimension) == 1 else \ 991 "({0},{1})".format(*dimension) 992 993 def star_operator(self): 994 msg = Message("Mysqlx.Expr.Expr") 995 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR") 996 msg["operator"] = Message("Mysqlx.Expr.Operator", name="*") 997 return msg 998 999 def atomic_expr(self): 1000 """Parse an atomic expression and return a protobuf Expr object""" 1001 token = self.next_token() 1002 1003 if token.token_type in [TokenType.EROTEME, TokenType.COLON]: 1004 return self.parse_place_holder(token) 1005 elif token.token_type == TokenType.LCURLY: 1006 return self.parse_json_doc() 1007 elif token.token_type == TokenType.LSQBRACKET: 1008 return self.parse_json_array() 1009 elif token.token_type == TokenType.CAST: 1010 return self.cast() 1011 elif token.token_type == TokenType.LPAREN: 1012 expr = self._expr() 1013 self.expect_token(TokenType.RPAREN) 1014 return expr 1015 elif token.token_type in [TokenType.PLUS, TokenType.MINUS]: 1016 peek = self.peek_token() 1017 if peek.token_type == TokenType.LNUM: 1018 self.tokens[self.pos].value = token.value + peek.value 1019 return self.atomic_expr() 1020 return build_unary_op(token.value, self.atomic_expr()) 1021 elif token.token_type in [TokenType.NOT, TokenType.NEG, TokenType.BANG]: 1022 return build_unary_op(token.value, self.atomic_expr()) 1023 elif token.token_type == TokenType.LSTRING: 1024 return build_literal_expr(build_string_scalar(token.value)) 1025 elif token.token_type == TokenType.NULL: 1026 return build_literal_expr(build_null_scalar()) 1027 elif token.token_type == TokenType.LNUM: 1028 if "." in token.value: 1029 return build_literal_expr( 1030 build_double_scalar(float(token.value))) 1031 return build_literal_expr(build_int_scalar(int(token.value))) 1032 elif token.token_type in [TokenType.TRUE, TokenType.FALSE]: 1033 return build_literal_expr( 1034 build_bool_scalar(token.token_type == TokenType.TRUE)) 1035 elif token.token_type == TokenType.DOLLAR: 1036 return self.document_field() 1037 elif token.token_type == TokenType.MUL: 1038 return self.star_operator() 1039 elif token.token_type == TokenType.IDENT: 1040 self.pos = self.pos - 1 # stay on the identifier 1041 if self.next_token_type_is(TokenType.LPAREN) or \ 1042 (self.next_token_type_is(TokenType.DOT) and 1043 self.pos_token_type_is(self.pos + 2, TokenType.IDENT) and 1044 self.pos_token_type_is(self.pos + 3, TokenType.LPAREN)): 1045 # Function call 1046 return self.function_call() 1047 return (self.document_field() 1048 if not self._allow_relational_columns 1049 else self.column_identifier()) 1050 1051 raise ValueError("Unknown token type = {0} when expecting atomic " 1052 "expression at {1}" 1053 "".format(token.token_type, self.pos)) 1054 1055 def parse_left_assoc_binary_op_expr(self, types, inner_parser): 1056 """Given a `set' of types and an Expr-returning inner parser function, 1057 parse a left associate binary operator expression""" 1058 lhs = inner_parser() 1059 while (self.pos < len(self.tokens) and 1060 self.tokens[self.pos].token_type in types): 1061 msg = Message("Mysqlx.Expr.Expr") 1062 msg["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR") 1063 operator = Message("Mysqlx.Expr.Operator") 1064 operator["name"] = _OPERATORS[self.tokens[self.pos].value] 1065 operator["param"] = [lhs.get_message()] 1066 self.pos += 1 1067 operator["param"].extend([inner_parser().get_message()]) 1068 msg["operator"] = operator 1069 lhs = msg 1070 return lhs 1071 1072 # operator precedence is implemented here 1073 def add_sub_interval(self): 1074 lhs = self.atomic_expr() 1075 if self.cur_token_type_in(TokenType.PLUS, TokenType.MINUS) and \ 1076 self.next_token_type_is(TokenType.INTERVAL): 1077 token = self.next_token() 1078 1079 operator = Message("Mysqlx.Expr.Operator") 1080 operator["param"].extend([lhs.get_message()]) 1081 operator["name"] = "date_add" if token.token_type is TokenType.PLUS \ 1082 else "date_sub" 1083 1084 self.consume_token(TokenType.INTERVAL) 1085 operator["param"].extend([self.bit_expr().get_message()]) 1086 1087 if not self.cur_token_type_in(*_INTERVAL_UNITS): 1088 raise ValueError("Expected interval type at position {0}" 1089 "".format(self.pos)) 1090 1091 token = str.encode(self.consume_any_token().upper()) 1092 operator["param"].extend([build_literal_expr( 1093 build_bytes_scalar(token)).get_message()]) 1094 1095 lhs = Message("Mysqlx.Expr.Expr", operator=operator) 1096 lhs["type"] = mysqlxpb_enum("Mysqlx.Expr.Expr.Type.OPERATOR") 1097 1098 return lhs 1099 1100 def mul_div_expr(self): 1101 return self.parse_left_assoc_binary_op_expr( 1102 set([TokenType.MUL, TokenType.DIV, TokenType.MOD]), 1103 self.add_sub_interval) 1104 1105 def add_sub_expr(self): 1106 return self.parse_left_assoc_binary_op_expr( 1107 set([TokenType.PLUS, TokenType.MINUS]), self.mul_div_expr) 1108 1109 def shift_expr(self): 1110 return self.parse_left_assoc_binary_op_expr( 1111 set([TokenType.LSHIFT, TokenType.RSHIFT]), self.add_sub_expr) 1112 1113 def bit_expr(self): 1114 return self.parse_left_assoc_binary_op_expr( 1115 set([TokenType.BITAND, TokenType.BITOR, TokenType.BITXOR]), 1116 self.shift_expr) 1117 1118 def comp_expr(self): 1119 return self.parse_left_assoc_binary_op_expr( 1120 set([TokenType.GE, TokenType.GT, TokenType.LE, TokenType.LT, 1121 TokenType.EQ, TokenType.NE]), self.bit_expr) 1122 1123 def ilri_expr(self): 1124 params = [] 1125 lhs = self.comp_expr() 1126 is_not = False 1127 if self.cur_token_type_is(TokenType.NOT): 1128 is_not = True 1129 self.consume_token(TokenType.NOT) 1130 if self.pos < len(self.tokens): 1131 params.append(lhs.get_message()) 1132 op_name = self.tokens[self.pos].value 1133 if self.cur_token_type_is(TokenType.IS): 1134 self.consume_token(TokenType.IS) 1135 # for IS, NOT comes AFTER 1136 if self.cur_token_type_is(TokenType.NOT): 1137 is_not = True 1138 self.consume_token(TokenType.NOT) 1139 params.append(self.comp_expr().get_message()) 1140 elif self.cur_token_type_is(TokenType.IN): 1141 self.consume_token(TokenType.IN) 1142 if self.cur_token_type_is(TokenType.LPAREN): 1143 params.extend(self.paren_expr_list()) 1144 else: 1145 op_name = "cont_in" 1146 params.append(self.comp_expr().get_message()) 1147 elif self.cur_token_type_is(TokenType.OVERLAPS): 1148 self.consume_token(TokenType.OVERLAPS) 1149 params.append(self.comp_expr().get_message()) 1150 1151 elif self.cur_token_type_is(TokenType.LIKE): 1152 self.consume_token(TokenType.LIKE) 1153 params.append(self.comp_expr().get_message()) 1154 if self.cur_token_type_is(TokenType.ESCAPE): 1155 self.consume_token(TokenType.ESCAPE) 1156 params.append(self.comp_expr().get_message()) 1157 elif self.cur_token_type_is(TokenType.BETWEEN): 1158 self.consume_token(TokenType.BETWEEN) 1159 params.append(self.comp_expr().get_message()) 1160 self.consume_token(TokenType.AND) 1161 params.append(self.comp_expr().get_message()) 1162 elif self.cur_token_type_is(TokenType.REGEXP): 1163 self.consume_token(TokenType.REGEXP) 1164 params.append(self.comp_expr().get_message()) 1165 else: 1166 if is_not: 1167 raise ValueError("Unknown token after NOT as pos {0}" 1168 "".format(self.pos)) 1169 op_name = None # not an operator we're interested in 1170 if op_name: 1171 operator = Message("Mysqlx.Expr.Operator") 1172 operator["name"] = _NEGATION[op_name] if is_not else op_name 1173 operator["param"] = params 1174 msg_expr = Message("Mysqlx.Expr.Expr") 1175 msg_expr["type"] = mysqlxpb_enum( 1176 "Mysqlx.Expr.Expr.Type.OPERATOR") 1177 msg_expr["operator"] = operator.get_message() 1178 lhs = msg_expr 1179 return lhs 1180 1181 def and_expr(self): 1182 return self.parse_left_assoc_binary_op_expr( 1183 set([TokenType.AND, TokenType.ANDAND]), self.ilri_expr) 1184 1185 def xor_expr(self): 1186 return self.parse_left_assoc_binary_op_expr( 1187 set([TokenType.XOR]), self.and_expr) 1188 1189 def or_expr(self): 1190 return self.parse_left_assoc_binary_op_expr( 1191 set([TokenType.OR, TokenType.OROR]), self.xor_expr) 1192 1193 def _expr(self, reparse=False): 1194 if reparse: 1195 self.tokens = [] 1196 self.pos = 0 1197 self.placeholder_name_to_position = {} 1198 self.positional_placeholder_count = 0 1199 self.lex() 1200 return self.or_expr() 1201 1202 def expr(self, reparse=False): 1203 expression = self._expr(reparse) 1204 used_tokens = self.pos 1205 if self.pos_token_type_is(len(self.tokens) - 2, TokenType.AS): 1206 used_tokens += 2 1207 if used_tokens < len(self.tokens): 1208 raise ValueError("Unused token types {} found in expression at " 1209 "position: {}".format(self.tokens[self.pos:], 1210 self.pos)) 1211 return expression 1212 1213 def parse_table_insert_field(self): 1214 return Message("Mysqlx.Crud.Column", 1215 name=self.consume_token(TokenType.IDENT)) 1216 1217 def parse_table_update_field(self): 1218 return self.column_identifier().identifier 1219 1220 def _table_fields(self): 1221 fields = [] 1222 temp = self.string.split(",") 1223 temp.reverse() 1224 while temp: 1225 field = temp.pop() 1226 while field.count("(") != field.count(")") or \ 1227 field.count("[") != field.count("]") or \ 1228 field.count("{") != field.count("}"): 1229 field = "{1},{0}".format(temp.pop(), field) 1230 fields.append(field.strip()) 1231 return fields 1232 1233 def parse_table_select_projection(self): 1234 project_expr = [] 1235 first = True 1236 fields = self._table_fields() 1237 while self.pos < len(self.tokens): 1238 if not first: 1239 self.consume_token(TokenType.COMMA) 1240 first = False 1241 projection = Message("Mysqlx.Crud.Projection", source=self._expr()) 1242 if self.cur_token_type_is(TokenType.AS): 1243 self.consume_token(TokenType.AS) 1244 projection["alias"] = self.consume_token(TokenType.IDENT) 1245 else: 1246 projection["alias"] = fields[len(project_expr)] 1247 project_expr.append(projection.get_message()) 1248 1249 return project_expr 1250 1251 def parse_order_spec(self): 1252 order_specs = [] 1253 first = True 1254 while self.pos < len(self.tokens): 1255 if not first: 1256 self.consume_token(TokenType.COMMA) 1257 first = False 1258 order = Message("Mysqlx.Crud.Order", expr=self._expr()) 1259 if self.cur_token_type_is(TokenType.ORDERBY_ASC): 1260 order["direction"] = mysqlxpb_enum( 1261 "Mysqlx.Crud.Order.Direction.ASC") 1262 self.consume_token(TokenType.ORDERBY_ASC) 1263 elif self.cur_token_type_is(TokenType.ORDERBY_DESC): 1264 order["direction"] = mysqlxpb_enum( 1265 "Mysqlx.Crud.Order.Direction.DESC") 1266 self.consume_token(TokenType.ORDERBY_DESC) 1267 order_specs.append(order.get_message()) 1268 return order_specs 1269 1270 def parse_expr_list(self): 1271 expr_list = [] 1272 first = True 1273 while self.pos < len(self.tokens): 1274 if not first: 1275 self.consume_token(TokenType.COMMA) 1276 first = False 1277 expr_list.append(self._expr().get_message()) 1278 return expr_list 1279