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