Skip to content

Commit 4f727e4

Browse files
authored
Merge pull request #83 from mindsdb/multi-statement
Exception message for multiple statements in single query
2 parents 7183a7b + 1bc1b70 commit 4f727e4

4 files changed

Lines changed: 70 additions & 4 deletions

File tree

mindsdb_sql_parser/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = 'mindsdb_sql_parser'
22
__package_name__ = 'mindsdb_sql_parser'
3-
__version__ = '0.13.7'
3+
__version__ = '0.13.8'
44
__description__ = "Mindsdb SQL parser"
55
__email__ = "jorge@mindsdb.com"
66
__author__ = 'MindsDB Inc'

mindsdb_sql_parser/__init__.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ def process(self) -> str:
2525
# show error location
2626
msgs = self.error_location()
2727

28+
if self.bad_token is not None and self.bad_token.value == ';':
29+
# unexpected semicolon in the middle of the query, it might be delimiter of statements
30+
msgs.append('Only a single sql statement is expected. Got multiple instead')
31+
return '\n'.join(msgs)
32+
2833
# suggestion
2934
suggestions = self.make_suggestion()
3035

@@ -171,11 +176,25 @@ def parse_sql(sql, dialect=None):
171176
from mindsdb_sql_parser.parser import MindsDBParser
172177
lexer, parser = MindsDBLexer(), MindsDBParser()
173178

174-
# remove ending semicolon and spaces
175-
sql = re.sub(r'[\s;]+$', '', sql)
179+
def semicolon_checker(generator):
180+
"""
181+
Repeat the same elements from generator except trailing SEMICOLON tokens.
182+
They are kept in buffer till any other token appear
183+
"""
184+
185+
buffer = []
186+
for token in generator:
187+
if token.type == 'SEMICOLON':
188+
buffer.append(token)
189+
continue
190+
elif len(buffer) > 0:
191+
for buf_token in buffer:
192+
yield buf_token
193+
buffer = []
194+
yield token
176195

177196
tokens = lexer.tokenize(sql)
178-
ast = parser.parse(tokens)
197+
ast = parser.parse(semicolon_checker(tokens))
179198

180199
if ast is None:
181200

mindsdb_sql_parser/parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
class MindsDBParser(Parser):
4444
log = ParserLogger()
4545
tokens = MindsDBLexer.tokens
46+
start = "query"
4647

4748
precedence = (
4849
('left', OR),

tests/test_base_sql/test_base_sql.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from textwrap import dedent
2+
3+
import pytest
4+
25
from mindsdb_sql_parser import parse_sql
6+
from mindsdb_sql_parser.exceptions import ParsingException
37

48
from mindsdb_sql_parser.ast import *
59

@@ -86,3 +90,45 @@ def test_quotes_identifier(self):
8690

8791
assert str(ast).lower() == str(expected_ast).lower()
8892
assert ast.to_tree() == expected_ast.to_tree()
93+
94+
def test_multy_statement(self):
95+
sql = """
96+
select 1;
97+
select 2
98+
"""
99+
100+
with pytest.raises(ParsingException) as excinfo:
101+
parse_sql(sql)
102+
103+
assert "Only a single sql statement is expected" in str(excinfo.value)
104+
105+
def test_trailing_semicolon(self):
106+
query = parse_sql("select 1;")
107+
assert query == Select(targets=[Constant(1)])
108+
109+
def test_comment_after_semicolon(self):
110+
sql = """
111+
select 1; -- my query
112+
"""
113+
114+
query = parse_sql(sql)
115+
assert query == Select(targets=[Constant(1)])
116+
117+
def test_comment_symbols_in_string(self):
118+
expected_query = Select(targets=[Constant('--x')])
119+
120+
query = parse_sql("select '--x'")
121+
assert query == expected_query
122+
123+
query = parse_sql('select "--x"')
124+
assert query == expected_query
125+
126+
# multiline
127+
expected_query = Select(targets=[Constant('/* x */')])
128+
129+
query = parse_sql("select '/* x */'")
130+
assert query == expected_query
131+
132+
query = parse_sql('select "/* x */"')
133+
assert query == expected_query
134+

0 commit comments

Comments
 (0)