Skip to content
Snippets Groups Projects
Commit 7aa6cc8a authored by Konstantin Osipov's avatar Konstantin Osipov
Browse files

Test-runner: extend the grammar to cover Bug#729758

Allow disjunctions in SELECT WHERE clause and LIMIT
clause in SELECT, to be able to cover the problem
reported in Bug#729758.

Fix a bug with prepare_gdb arguments (remove an extra
space in -e option).

Add a simple test for Bug#729758.

The following bugs were reported in the process:

Bug#729789 "Zero limit is treated the same as no limit"
Bug#730593 "Bad data if incomplete tuple"
Bug#730613 "SELECT with a disjunction on multiple keys returns
matching tuples twice"
parent a91c4bcc
No related branches found
No related tags found
No related merge requests found
insert into t0 values ('Doe', 'Richard')
Insert OK, 1 row affected
insert into t0 values ('Roe', 'Richard')
Insert OK, 1 row affected
insert into t0 values ('Woe', 'Richard')
Insert OK, 1 row affected
insert into t0 values ('Major', 'Tomas')
Insert OK, 1 row affected
insert into t0 values ('Kytes', 'Tomas')
Insert OK, 1 row affected
insert into t0 values ('Stiles', 'Tomas')
Insert OK, 1 row affected
insert into t0 values ('Wales', 'Tomas')
Insert OK, 1 row affected
insert into t0 values ('Callaghan', 'Tomas')
Insert OK, 1 row affected
select * from t0 where k1='Richard' or k1='Tomas' or k1='Tomas' limit 5
An error occurred: ERR_CODE_ILLEGAL_PARAMS, 'Illegal parameters'
# encoding: tarantool
exec sql "insert into t0 values ('Doe', 'Richard')"
exec sql "insert into t0 values ('Roe', 'Richard')"
exec sql "insert into t0 values ('Woe', 'Richard')"
exec sql "insert into t0 values ('Major', 'Tomas')"
exec sql "insert into t0 values ('Kytes', 'Tomas')"
exec sql "insert into t0 values ('Stiles', 'Tomas')"
exec sql "insert into t0 values ('Wales', 'Tomas')"
exec sql "insert into t0 values ('Callaghan', 'Tomas')"
# xxx: bug
exec sql "select * from t0 where k1='Richard' or k1='Tomas' or k1='Tomas' limit 5"
# vim: syntax=python
[default]
description = tarantool/silverbox, various namespace configurations and properties
config = tarantool.cfg
# put disabled tests here
#disabled = sql.test
slab_alloc_arena = 0.1
pid_file = "box.pid"
# Use -a not -a to work correctly on FreeBSD
#
logger="tee -a tarantool.log"
primary_port = 33013
secondary_port = 33014
admin_port = 33015
rows_per_wal = 50
namespace[0].enabled = 1
namespace[0].index[0].type = "HASH"
namespace[0].index[0].unique = 1
namespace[0].index[0].key_field[0].fieldno = 0
namespace[0].index[0].key_field[0].type = "STR"
namespace[0].index[1].type = "TREE"
namespace[0].index[1].unique = 0
namespace[0].index[1].key_field[0].fieldno = 1
namespace[0].index[1].key_field[0].type = "STR"
...@@ -5,6 +5,10 @@ object_no_re = re.compile("[a-z_]*", re.I) ...@@ -5,6 +5,10 @@ object_no_re = re.compile("[a-z_]*", re.I)
%% %%
# The grammar below solely covers the functionality provided by
# Tarantool binary protocol, from which follow all the
# limitations. For reference please see doc/box-protocol.txt.
parser sql: parser sql:
ignore: '\\s+' ignore: '\\s+'
...@@ -21,6 +25,8 @@ parser sql: ...@@ -21,6 +25,8 @@ parser sql:
token WHERE: 'where' token WHERE: 'where'
token VALUES: 'values' token VALUES: 'values'
token SET: 'set' token SET: 'set'
token OR: 'or'
token LIMIT: 'limit'
token END: '\\s*$' token END: '\\s*$'
rule sql: (insert {{ stmt = insert }} | rule sql: (insert {{ stmt = insert }} |
...@@ -31,19 +37,27 @@ parser sql: ...@@ -31,19 +37,27 @@ parser sql:
rule insert: INSERT [INTO] ident VALUES value_list rule insert: INSERT [INTO] ident VALUES value_list
{{ return sql_ast.StatementInsert(ident, value_list) }} {{ return sql_ast.StatementInsert(ident, value_list) }}
rule update: UPDATE ident SET update_list opt_where rule update: UPDATE ident SET update_list opt_simple_where
{{ return sql_ast.StatementUpdate(ident, update_list, opt_where) }} {{ return sql_ast.StatementUpdate(ident, update_list, opt_simple_where) }}
rule delete: DELETE FROM ident opt_where rule delete: DELETE FROM ident opt_simple_where
{{ return sql_ast.StatementDelete(ident, opt_where) }} {{ return sql_ast.StatementDelete(ident, opt_simple_where) }}
rule select: SELECT '\*' FROM ident opt_where rule select: SELECT '\*' FROM ident opt_where opt_limit
{{ return sql_ast.StatementSelect(ident, opt_where) }} {{ return sql_ast.StatementSelect(ident, opt_where, opt_limit) }}
rule ping: PING rule ping: PING
{{ return sql_ast.StatementPing() }} {{ return sql_ast.StatementPing() }}
rule predicate: ident '=' constant rule predicate: ident '=' constant
{{ return (ident, constant) }} {{ return (ident, constant) }}
rule opt_where: {{ return None }} rule opt_simple_where: {{ return None }}
| WHERE predicate | WHERE predicate
{{ return predicate }} {{ return predicate }}
rule opt_where: {{ return None }}
| WHERE disjunction
{{ return disjunction }}
rule disjunction: predicate {{ disjunction = [predicate] }}
[(OR predicate {{ disjunction.append(predicate) }})+]
{{ return disjunction }}
rule opt_limit: {{ return 0xffffffff }}
| LIMIT NUM {{ return int(NUM) }}
rule value_list: '\(' expr {{ value_list = [expr] }} rule value_list: '\(' expr {{ value_list = [expr] }}
[("," expr {{ value_list.append(expr) }} )+] [("," expr {{ value_list.append(expr) }} )+]
'\)' {{ return value_list }} '\)' {{ return value_list }}
......
...@@ -30,6 +30,8 @@ class sqlScanner(runtime.Scanner): ...@@ -30,6 +30,8 @@ class sqlScanner(runtime.Scanner):
('WHERE', re.compile('where')), ('WHERE', re.compile('where')),
('VALUES', re.compile('values')), ('VALUES', re.compile('values')),
('SET', re.compile('set')), ('SET', re.compile('set')),
('OR', re.compile('or')),
('LIMIT', re.compile('limit')),
('END', re.compile('\\s*$')), ('END', re.compile('\\s*$')),
] ]
def __init__(self, str,*args,**kw): def __init__(self, str,*args,**kw):
...@@ -74,16 +76,16 @@ class sql(runtime.Parser): ...@@ -74,16 +76,16 @@ class sql(runtime.Parser):
ident = self.ident(_context) ident = self.ident(_context)
SET = self._scan('SET', context=_context) SET = self._scan('SET', context=_context)
update_list = self.update_list(_context) update_list = self.update_list(_context)
opt_where = self.opt_where(_context) opt_simple_where = self.opt_simple_where(_context)
return sql_ast.StatementUpdate(ident, update_list, opt_where) return sql_ast.StatementUpdate(ident, update_list, opt_simple_where)
def delete(self, _parent=None): def delete(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'delete', []) _context = self.Context(_parent, self._scanner, 'delete', [])
DELETE = self._scan('DELETE', context=_context) DELETE = self._scan('DELETE', context=_context)
FROM = self._scan('FROM', context=_context) FROM = self._scan('FROM', context=_context)
ident = self.ident(_context) ident = self.ident(_context)
opt_where = self.opt_where(_context) opt_simple_where = self.opt_simple_where(_context)
return sql_ast.StatementDelete(ident, opt_where) return sql_ast.StatementDelete(ident, opt_simple_where)
def select(self, _parent=None): def select(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'select', []) _context = self.Context(_parent, self._scanner, 'select', [])
...@@ -92,7 +94,8 @@ class sql(runtime.Parser): ...@@ -92,7 +94,8 @@ class sql(runtime.Parser):
FROM = self._scan('FROM', context=_context) FROM = self._scan('FROM', context=_context)
ident = self.ident(_context) ident = self.ident(_context)
opt_where = self.opt_where(_context) opt_where = self.opt_where(_context)
return sql_ast.StatementSelect(ident, opt_where) opt_limit = self.opt_limit(_context)
return sql_ast.StatementSelect(ident, opt_where, opt_limit)
def ping(self, _parent=None): def ping(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'ping', []) _context = self.Context(_parent, self._scanner, 'ping', [])
...@@ -106,8 +109,8 @@ class sql(runtime.Parser): ...@@ -106,8 +109,8 @@ class sql(runtime.Parser):
constant = self.constant(_context) constant = self.constant(_context)
return (ident, constant) return (ident, constant)
def opt_where(self, _parent=None): def opt_simple_where(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'opt_where', []) _context = self.Context(_parent, self._scanner, 'opt_simple_where', [])
_token = self._peek('WHERE', 'END', context=_context) _token = self._peek('WHERE', 'END', context=_context)
if _token == 'END': if _token == 'END':
return None return None
...@@ -116,6 +119,38 @@ class sql(runtime.Parser): ...@@ -116,6 +119,38 @@ class sql(runtime.Parser):
predicate = self.predicate(_context) predicate = self.predicate(_context)
return predicate return predicate
def opt_where(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'opt_where', [])
_token = self._peek('WHERE', 'LIMIT', 'END', context=_context)
if _token != 'WHERE':
return None
else: # == 'WHERE'
WHERE = self._scan('WHERE', context=_context)
disjunction = self.disjunction(_context)
return disjunction
def disjunction(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'disjunction', [])
predicate = self.predicate(_context)
disjunction = [predicate]
if self._peek('OR', 'LIMIT', 'END', context=_context) == 'OR':
while 1:
OR = self._scan('OR', context=_context)
predicate = self.predicate(_context)
disjunction.append(predicate)
if self._peek('OR', 'LIMIT', 'END', context=_context) != 'OR': break
return disjunction
def opt_limit(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'opt_limit', [])
_token = self._peek('LIMIT', 'END', context=_context)
if _token == 'END':
return 0xffffffff
else: # == 'LIMIT'
LIMIT = self._scan('LIMIT', context=_context)
NUM = self._scan('NUM', context=_context)
return int(NUM)
def value_list(self, _parent=None): def value_list(self, _parent=None):
_context = self.Context(_parent, self._scanner, 'value_list', []) _context = self.Context(_parent, self._scanner, 'value_list', [])
self._scan("'\\('", context=_context) self._scan("'\\('", context=_context)
......
...@@ -242,16 +242,22 @@ class StatementDelete(StatementPing): ...@@ -242,16 +242,22 @@ class StatementDelete(StatementPing):
class StatementSelect(StatementPing): class StatementSelect(StatementPing):
reqeust_type = SELECT_REQUEST_TYPE reqeust_type = SELECT_REQUEST_TYPE
def __init__(self, table_name, where): def __init__(self, table_name, where, limit):
self.namespace_no = table_name self.namespace_no = table_name
if where: self.index_no = None
(self.index_no, key) = where self.key_list = []
self.key = [key] if not where:
else:
self.index_no = 0 self.index_no = 0
self.key = [""] self.key_list = ["",]
else:
for (index_no, key) in where:
self.key_list.append(key)
if self.index_no == None:
self.index_no = index_no
elif self.index_no != index_no:
raise RuntimeError("All key values in a disjunction must refer to the same index")
self.offset = 0 self.offset = 0
self.limit = 0xffffffff self.limit = limit
def pack(self): def pack(self):
buf = ctypes.create_string_buffer(PACKET_BUF_LEN) buf = ctypes.create_string_buffer(PACKET_BUF_LEN)
...@@ -260,8 +266,10 @@ class StatementSelect(StatementPing): ...@@ -260,8 +266,10 @@ class StatementSelect(StatementPing):
self.index_no, self.index_no,
self.offset, self.offset,
self.limit, self.limit,
1) len(self.key_list))
(buf, offset) = pack_tuple(self.key, buf, SELECT_REQUEST_FIXED_LEN) offset = SELECT_REQUEST_FIXED_LEN
for key in self.key_list:
(buf, offset) = pack_tuple([key], buf, offset)
return buf[:offset] return buf[:offset]
......
...@@ -33,7 +33,7 @@ def prepare_gdb(args): ...@@ -33,7 +33,7 @@ def prepare_gdb(args):
if term not in ["xterm", "rxvt", "urxvt", "gnome-terminal", "konsole"]: if term not in ["xterm", "rxvt", "urxvt", "gnome-terminal", "konsole"]:
raise RuntimeError("--gdb: unsupported terminal {0}".format(term)) raise RuntimeError("--gdb: unsupported terminal {0}".format(term))
args = [ term, "-e ", "gdb", "-ex", "break main", "-ex", "run"] + args args = [ term, "-e", "gdb", "-ex", "break main", "-ex", "run"] + args
return args return args
......
...@@ -260,6 +260,8 @@ class TestSuite: ...@@ -260,6 +260,8 @@ class TestSuite:
def run_all(self): def run_all(self):
"""For each file in the test suite, run client program """For each file in the test suite, run client program
assuming each file represents an individual test.""" assuming each file represents an individual test."""
if len(self.tests) == 0:
return 0
server = TarantoolSilverboxServer(self.args, self.ini) server = TarantoolSilverboxServer(self.args, self.ini)
server.install() server.install()
server.start() server.start()
......
...@@ -60,8 +60,9 @@ class Options: ...@@ -60,8 +60,9 @@ class Options:
dest = 'suites', dest = 'suites',
metavar = "suite", metavar = "suite",
nargs="*", nargs="*",
default = ["box"], default = ["box", "box_big"],
help = """List of tests suites to look for tests in. Default: "box".""") help = """List of tests suites to look for tests in. Default: "box",
"box_big".""")
parser.add_argument( parser.add_argument(
"--force", "--force",
...@@ -156,7 +157,7 @@ def main(): ...@@ -156,7 +157,7 @@ def main():
suites.append(TestSuite(suite_name, options.args)) suites.append(TestSuite(suite_name, options.args))
for suite in suites: for suite in suites:
failed_tests = suite.run_all() failed_tests += suite.run_all()
except RuntimeError as e: except RuntimeError as e:
print "\nFatal error: {0}. Execution aborted.".format(e) print "\nFatal error: {0}. Execution aborted.".format(e)
return (-1) return (-1)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment