Skip to content

Commit c6e9189

Browse files
authored
Merge pull request #416 from mindsdb/staging
Release 0.21.0
2 parents f747b5b + 5a7cf55 commit c6e9189

File tree

22 files changed

+565
-182
lines changed

22 files changed

+565
-182
lines changed

mindsdb_sql/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = 'mindsdb_sql'
22
__package_name__ = 'mindsdb_sql'
3-
__version__ = '0.20.0'
3+
__version__ = '0.21.0'
44
__description__ = "Pure python SQL parser"
55
__email__ = "[email protected]"
66
__author__ = 'MindsDB Inc'

mindsdb_sql/parser/ast/select/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .select import Select
22
from .common_table_expression import CommonTableExpression
3-
from .union import Union
3+
from .union import Union, Except, Intersect
44
from .constant import Constant, NullConstant, Last
55
from .star import Star
66
from .identifier import Identifier

mindsdb_sql/parser/ast/select/case.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55

66
class Case(ASTNode):
7-
def __init__(self, rules, default=None, *args, **kwargs):
7+
def __init__(self, rules, default=None, arg=None, *args, **kwargs):
88
super().__init__(*args, **kwargs)
99

1010
# structure:
1111
# [
1212
# [ condition, result ]
1313
# ]
14+
self.arg = arg
1415
self.rules = rules
1516
self.default = default
1617

@@ -36,7 +37,12 @@ def to_tree(self, *args, level=0, **kwargs):
3637
if self.default is not None:
3738
default_str = f'{ind1}default => {self.default.to_string()}\n'
3839

40+
arg_str = ''
41+
if self.arg is not None:
42+
arg_str = f'{ind1}arg => {self.arg.to_string()}\n'
43+
3944
return f'{ind}Case(\n' \
45+
f'{arg_str}'\
4046
f'{rules_str}\n' \
4147
f'{default_str}' \
4248
f'{ind})'
@@ -53,4 +59,8 @@ def get_string(self, *args, alias=True, **kwargs):
5359
default_str = ''
5460
if self.default is not None:
5561
default_str = f' ELSE {self.default.to_string()}'
56-
return f"CASE {rules_str}{default_str} END"
62+
63+
arg_str = ''
64+
if self.arg is not None:
65+
arg_str = f'{self.arg.to_string()} '
66+
return f"CASE {arg_str}{rules_str}{default_str} END"

mindsdb_sql/parser/ast/select/operation.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,13 @@ def get_string(self, *args, **kwargs):
9898

9999

100100
class WindowFunction(ASTNode):
101-
def __init__(self, function, partition=None, order_by=None, alias=None):
101+
def __init__(self, function, partition=None, order_by=None, alias=None, modifier=None):
102102
super().__init__()
103103
self.function = function
104104
self.partition = partition
105105
self.order_by = order_by
106106
self.alias = alias
107+
self.modifier = modifier
107108

108109
def to_tree(self, *args, level=0, **kwargs):
109110
fnc_str = self.function.to_tree(level=level+2)
@@ -143,7 +144,8 @@ def to_string(self, *args, **kwargs):
143144
alias_str = self.alias.to_string()
144145
else:
145146
alias_str = ''
146-
return f'{fnc_str} over({partition_str} {order_str}) {alias_str}'
147+
modifier_str = ' ' + self.modifier if self.modifier else ''
148+
return f'{fnc_str} over({partition_str} {order_str}{modifier_str}) {alias_str}'
147149

148150

149151
class Object(ASTNode):
@@ -177,7 +179,12 @@ def __init__(self, info):
177179
super().__init__(op='interval', args=[info, ])
178180

179181
def get_string(self, *args, **kwargs):
180-
return f'INTERVAL {self.args[0]}'
182+
183+
arg = self.args[0]
184+
items = arg.split(' ', maxsplit=1)
185+
# quote first element
186+
items[0] = f"'{items[0]}'"
187+
return "INTERVAL " + " ".join(items)
181188

182189
def to_tree(self, *args, level=0, **kwargs):
183190
return self.get_string( *args, **kwargs)

mindsdb_sql/parser/ast/select/type_cast.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33

44

55
class TypeCast(ASTNode):
6-
def __init__(self, type_name, arg, length=None, *args, **kwargs):
6+
def __init__(self, type_name, arg, precision=None, *args, **kwargs):
77
super().__init__(*args, **kwargs)
88

99
self.type_name = type_name
1010
self.arg = arg
11-
self.length = length
11+
self.precision = precision
1212

1313
def to_tree(self, *args, level=0, **kwargs):
14-
out_str = indent(level) + f'TypeCast(type_name={repr(self.type_name)}, length={self.length}, arg=\n{indent(level+1)}{self.arg.to_tree()})'
14+
out_str = indent(level) + f'TypeCast(type_name={repr(self.type_name)}, precision={self.precision}, arg=\n{indent(level+1)}{self.arg.to_tree()})'
1515
return out_str
1616

1717
def get_string(self, *args, **kwargs):
1818
type_name = self.type_name
19-
if self.length is not None:
20-
type_name += f'({self.length})'
19+
if self.precision is not None:
20+
precision = map(str, self.precision)
21+
type_name += f'({",".join(precision)})'
2122
return f'CAST({str(self.arg)} AS {type_name})'

mindsdb_sql/parser/ast/select/union.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
from mindsdb_sql.parser.utils import indent
33

44

5-
class Union(ASTNode):
5+
class CombiningQuery(ASTNode):
6+
operation = None
67

78
def __init__(self,
89
left,
@@ -24,7 +25,8 @@ def to_tree(self, *args, level=0, **kwargs):
2425
left_str = f'\n{ind1}left=\n{self.left.to_tree(level=level + 2)},'
2526
right_str = f'\n{ind1}right=\n{self.right.to_tree(level=level + 2)},'
2627

27-
out_str = f'{ind}Union(unique={repr(self.unique)},' \
28+
cls_name = self.__class__.__name__
29+
out_str = f'{ind}{cls_name}(unique={repr(self.unique)},' \
2830
f'{left_str}' \
2931
f'{right_str}' \
3032
f'\n{ind})'
@@ -33,7 +35,21 @@ def to_tree(self, *args, level=0, **kwargs):
3335
def get_string(self, *args, **kwargs):
3436
left_str = str(self.left)
3537
right_str = str(self.right)
36-
keyword = 'UNION' if self.unique else 'UNION ALL'
38+
keyword = self.operation
39+
if not self.unique:
40+
keyword += ' ALL'
3741
out_str = f"""{left_str}\n{keyword}\n{right_str}"""
3842

3943
return out_str
44+
45+
46+
class Union(CombiningQuery):
47+
operation = 'UNION'
48+
49+
50+
class Intersect(CombiningQuery):
51+
operation = 'INTERSECT'
52+
53+
54+
class Except(CombiningQuery):
55+
operation = 'EXCEPT'

mindsdb_sql/parser/dialects/mindsdb/lexer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class MindsDBLexer(Lexer):
5555

5656
JOIN, INNER, OUTER, CROSS, LEFT, RIGHT, ON,
5757

58-
UNION, ALL,
58+
UNION, ALL, INTERSECT, EXCEPT,
5959

6060
# CASE
6161
CASE, ELSE, END, THEN, WHEN,
@@ -238,6 +238,8 @@ class MindsDBLexer(Lexer):
238238
# UNION
239239

240240
UNION = r'\bUNION\b'
241+
INTERSECT = r'\bINTERSECT\b'
242+
EXCEPT = r'\bEXCEPT\b'
241243
ALL = r'\bALL\b'
242244

243245
# CASE

mindsdb_sql/parser/dialects/mindsdb/parser.py

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class MindsDBParser(Parser):
4646
('nonassoc', LESS, LEQ, GREATER, GEQ, IN, NOT_IN, BETWEEN, IS, IS_NOT, NOT_LIKE, LIKE),
4747
('left', JSON_GET),
4848
('left', PLUS, MINUS),
49-
('left', STAR, DIVIDE, TYPECAST),
49+
('left', STAR, DIVIDE, TYPECAST, MODULO),
5050
('right', UMINUS), # Unary minus operator, unary not
5151

5252
)
@@ -68,9 +68,9 @@ class MindsDBParser(Parser):
6868
'drop_predictor',
6969
'drop_datasource',
7070
'drop_dataset',
71-
'union',
7271
'select',
7372
'insert',
73+
'union',
7474
'update',
7575
'delete',
7676
'evaluate',
@@ -615,10 +615,13 @@ def update(self, p):
615615

616616
# INSERT
617617
@_('INSERT INTO identifier LPAREN column_list RPAREN select',
618-
'INSERT INTO identifier select')
618+
'INSERT INTO identifier LPAREN column_list RPAREN union',
619+
'INSERT INTO identifier select',
620+
'INSERT INTO identifier union')
619621
def insert(self, p):
620622
columns = getattr(p, 'column_list', None)
621-
return Insert(table=p.identifier, columns=columns, from_select=p.select)
623+
query = p.select if hasattr(p, 'select') else p.union
624+
return Insert(table=p.identifier, columns=columns, from_select=query)
622625

623626
@_('INSERT INTO identifier LPAREN column_list RPAREN VALUES expr_list_set',
624627
'INSERT INTO identifier VALUES expr_list_set')
@@ -999,21 +1002,35 @@ def database_engine(self, p):
9991002
engine = p.string
10001003
return {'identifier':p.identifier, 'engine':engine, 'if_not_exists':p.if_not_exists_or_empty}
10011004

1002-
# UNION / UNION ALL
1005+
# Combining
10031006
@_('select UNION select',
1004-
'union UNION select')
1007+
'union UNION select',
1008+
'select UNION ALL select',
1009+
'union UNION ALL select')
10051010
def union(self, p):
1006-
return Union(left=p[0], right=p[2], unique=True)
1011+
unique = not hasattr(p, 'ALL')
1012+
return Union(left=p[0], right=p[2] if unique else p[3], unique=unique)
10071013

1008-
@_('select UNION ALL select',
1009-
'union UNION ALL select',)
1014+
@_('select INTERSECT select',
1015+
'union INTERSECT select',
1016+
'select INTERSECT ALL select',
1017+
'union INTERSECT ALL select')
1018+
def union(self, p):
1019+
unique = not hasattr(p, 'ALL')
1020+
return Intersect(left=p[0], right=p[2] if unique else p[3], unique=unique)
1021+
@_('select EXCEPT select',
1022+
'union EXCEPT select',
1023+
'select EXCEPT ALL select',
1024+
'union EXCEPT ALL select')
10101025
def union(self, p):
1011-
return Union(left=p[0], right=p[3], unique=False)
1026+
unique = not hasattr(p, 'ALL')
1027+
return Except(left=p[0], right=p[2] if unique else p[3], unique=unique)
10121028

10131029
# tableau
10141030
@_('LPAREN select RPAREN')
1031+
@_('LPAREN union RPAREN')
10151032
def select(self, p):
1016-
return p.select
1033+
return p[1]
10171034

10181035
# WITH
10191036
@_('ctes select')
@@ -1033,13 +1050,14 @@ def ctes(self, p):
10331050
]
10341051
return ctes
10351052

1036-
@_('WITH identifier cte_columns_or_nothing AS LPAREN select RPAREN')
1053+
@_('WITH identifier cte_columns_or_nothing AS LPAREN select RPAREN',
1054+
'WITH identifier cte_columns_or_nothing AS LPAREN union RPAREN')
10371055
def ctes(self, p):
10381056
return [
10391057
CommonTableExpression(
10401058
name=p.identifier,
10411059
columns=p.cte_columns_or_nothing,
1042-
query=p.select)
1060+
query=p[5])
10431061
]
10441062

10451063
@_('empty')
@@ -1334,6 +1352,15 @@ def column_list(self, p):
13341352
def case(self, p):
13351353
return Case(rules=p.case_conditions, default=getattr(p, 'expr', None))
13361354

1355+
@_('CASE expr case_conditions ELSE expr END',
1356+
'CASE expr case_conditions END')
1357+
def case(self, p):
1358+
if hasattr(p, 'expr'):
1359+
arg, default = p.expr, None
1360+
else:
1361+
arg, default = p.expr0, p.expr1
1362+
return Case(rules=p.case_conditions, default=default, arg=arg)
1363+
13371364
@_('case_condition',
13381365
'case_conditions case_condition')
13391366
def case_conditions(self, p):
@@ -1346,13 +1373,18 @@ def case_condition(self, p):
13461373
return [p.expr0, p.expr1]
13471374

13481375
# Window function
1349-
@_('function OVER LPAREN window RPAREN')
1376+
@_('expr OVER LPAREN window RPAREN',
1377+
'expr OVER LPAREN window id BETWEEN id id AND id id RPAREN')
13501378
def window_function(self, p):
13511379

1380+
modifier = None
1381+
if hasattr(p, 'BETWEEN'):
1382+
modifier = f'{p.id0} BETWEEN {p.id1} {p.id2} AND {p.id3} {p.id4}'
13521383
return WindowFunction(
1353-
function=p.function,
1384+
function=p.expr,
13541385
order_by=p.window.get('order_by'),
13551386
partition=p.window.get('partition'),
1387+
modifier=modifier,
13561388
)
13571389

13581390
@_('window PARTITION_BY expr_list')
@@ -1469,8 +1501,13 @@ def expr_list_or_nothing(self, p):
14691501
pass
14701502

14711503
@_('CAST LPAREN expr AS id LPAREN integer RPAREN RPAREN')
1504+
@_('CAST LPAREN expr AS id LPAREN integer COMMA integer RPAREN RPAREN')
14721505
def expr(self, p):
1473-
return TypeCast(arg=p.expr, type_name=str(p.id), length=p.integer)
1506+
if hasattr(p, 'integer'):
1507+
precision=[p.integer]
1508+
else:
1509+
precision=[p.integer0, p.integer1]
1510+
return TypeCast(arg=p.expr, type_name=str(p.id), precision=precision)
14741511

14751512
@_('CAST LPAREN expr AS id RPAREN')
14761513
def expr(self, p):

mindsdb_sql/parser/dialects/mysql/parser.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,8 +821,13 @@ def expr_list_or_nothing(self, p):
821821
pass
822822

823823
@_('CAST LPAREN expr AS id LPAREN integer RPAREN RPAREN')
824+
@_('CAST LPAREN expr AS id LPAREN integer COMMA integer RPAREN RPAREN')
824825
def expr(self, p):
825-
return TypeCast(arg=p.expr, type_name=str(p.id), length=p.integer)
826+
if hasattr(p, 'integer'):
827+
precision=[p.integer]
828+
else:
829+
precision=[p.integer0, p.integer1]
830+
return TypeCast(arg=p.expr, type_name=str(p.id), precision=precision)
826831

827832
@_('CAST LPAREN expr AS id RPAREN')
828833
def expr(self, p):

mindsdb_sql/parser/parser.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,8 +581,13 @@ def expr_list_or_nothing(self, p):
581581
pass
582582

583583
@_('CAST LPAREN expr AS id LPAREN integer RPAREN RPAREN')
584+
@_('CAST LPAREN expr AS id LPAREN integer COMMA integer RPAREN RPAREN')
584585
def expr(self, p):
585-
return TypeCast(arg=p.expr, type_name=str(p.id), length=p.integer)
586+
if hasattr(p, 'integer'):
587+
precision=[p.integer]
588+
else:
589+
precision=[p.integer0, p.integer1]
590+
return TypeCast(arg=p.expr, type_name=str(p.id), precision=precision)
586591

587592
@_('CAST LPAREN expr AS id RPAREN')
588593
def expr(self, p):

0 commit comments

Comments
 (0)