diff --git a/test/box_big/sql.result b/test/box_big/sql.result new file mode 100644 index 0000000000000000000000000000000000000000..5e9737fdf00dd47ddea9836312a004a97daf8bd7 --- /dev/null +++ b/test/box_big/sql.result @@ -0,0 +1,18 @@ +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' diff --git a/test/box_big/sql.test b/test/box_big/sql.test new file mode 100644 index 0000000000000000000000000000000000000000..8fc4b331350db63cbb06aab32befde1f537d067b --- /dev/null +++ b/test/box_big/sql.test @@ -0,0 +1,14 @@ +# 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 diff --git a/test/box_big/suite.ini b/test/box_big/suite.ini new file mode 100644 index 0000000000000000000000000000000000000000..8c7fe6ef267ab8a5c4fe0cd31165fc6fabfa778a --- /dev/null +++ b/test/box_big/suite.ini @@ -0,0 +1,5 @@ +[default] +description = tarantool/silverbox, various namespace configurations and properties +config = tarantool.cfg +# put disabled tests here +#disabled = sql.test diff --git a/test/box_big/tarantool.cfg b/test/box_big/tarantool.cfg new file mode 100644 index 0000000000000000000000000000000000000000..fbbe26eeb6082521bb178ce46d82600d919269ee --- /dev/null +++ b/test/box_big/tarantool.cfg @@ -0,0 +1,24 @@ +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" diff --git a/test/lib/sql.g b/test/lib/sql.g index 087fff5383b30dd8c4cc0f927fee1aec045c375d..86d2434d573de2f327dab3c12a8544a384881706 100644 --- a/test/lib/sql.g +++ b/test/lib/sql.g @@ -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: ignore: '\\s+' @@ -21,6 +25,8 @@ parser sql: token WHERE: 'where' token VALUES: 'values' token SET: 'set' + token OR: 'or' + token LIMIT: 'limit' token END: '\\s*$' rule sql: (insert {{ stmt = insert }} | @@ -31,19 +37,27 @@ parser sql: rule insert: INSERT [INTO] ident VALUES value_list {{ return sql_ast.StatementInsert(ident, value_list) }} - rule update: UPDATE ident SET update_list opt_where - {{ return sql_ast.StatementUpdate(ident, update_list, opt_where) }} - rule delete: DELETE FROM ident opt_where - {{ return sql_ast.StatementDelete(ident, opt_where) }} - rule select: SELECT '\*' FROM ident opt_where - {{ return sql_ast.StatementSelect(ident, opt_where) }} + rule update: UPDATE ident SET update_list opt_simple_where + {{ return sql_ast.StatementUpdate(ident, update_list, opt_simple_where) }} + rule delete: DELETE FROM ident opt_simple_where + {{ return sql_ast.StatementDelete(ident, opt_simple_where) }} + rule select: SELECT '\*' FROM ident opt_where opt_limit + {{ return sql_ast.StatementSelect(ident, opt_where, opt_limit) }} rule ping: PING {{ return sql_ast.StatementPing() }} rule predicate: ident '=' constant {{ return (ident, constant) }} - rule opt_where: {{ return None }} + rule opt_simple_where: {{ return None }} | WHERE 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] }} [("," expr {{ value_list.append(expr) }} )+] '\)' {{ return value_list }} diff --git a/test/lib/sql.py b/test/lib/sql.py index f1ef9ca2542ecdece96b5e3cb8e1742a250fbefd..67d75319ed2ba345919d5f3bfa29183ab0c9424a 100644 --- a/test/lib/sql.py +++ b/test/lib/sql.py @@ -30,6 +30,8 @@ class sqlScanner(runtime.Scanner): ('WHERE', re.compile('where')), ('VALUES', re.compile('values')), ('SET', re.compile('set')), + ('OR', re.compile('or')), + ('LIMIT', re.compile('limit')), ('END', re.compile('\\s*$')), ] def __init__(self, str,*args,**kw): @@ -74,16 +76,16 @@ class sql(runtime.Parser): ident = self.ident(_context) SET = self._scan('SET', context=_context) update_list = self.update_list(_context) - opt_where = self.opt_where(_context) - return sql_ast.StatementUpdate(ident, update_list, opt_where) + opt_simple_where = self.opt_simple_where(_context) + return sql_ast.StatementUpdate(ident, update_list, opt_simple_where) def delete(self, _parent=None): _context = self.Context(_parent, self._scanner, 'delete', []) DELETE = self._scan('DELETE', context=_context) FROM = self._scan('FROM', context=_context) ident = self.ident(_context) - opt_where = self.opt_where(_context) - return sql_ast.StatementDelete(ident, opt_where) + opt_simple_where = self.opt_simple_where(_context) + return sql_ast.StatementDelete(ident, opt_simple_where) def select(self, _parent=None): _context = self.Context(_parent, self._scanner, 'select', []) @@ -92,7 +94,8 @@ class sql(runtime.Parser): FROM = self._scan('FROM', context=_context) ident = self.ident(_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): _context = self.Context(_parent, self._scanner, 'ping', []) @@ -106,8 +109,8 @@ class sql(runtime.Parser): constant = self.constant(_context) return (ident, constant) - def opt_where(self, _parent=None): - _context = self.Context(_parent, self._scanner, 'opt_where', []) + def opt_simple_where(self, _parent=None): + _context = self.Context(_parent, self._scanner, 'opt_simple_where', []) _token = self._peek('WHERE', 'END', context=_context) if _token == 'END': return None @@ -116,6 +119,38 @@ class sql(runtime.Parser): predicate = self.predicate(_context) 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): _context = self.Context(_parent, self._scanner, 'value_list', []) self._scan("'\\('", context=_context) diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py index 515340ccdaf994f78d4801b40eab3d7784c51c76..45050ad085dd0de6f054a3fd338e591620294d88 100644 --- a/test/lib/sql_ast.py +++ b/test/lib/sql_ast.py @@ -242,16 +242,22 @@ class StatementDelete(StatementPing): class StatementSelect(StatementPing): reqeust_type = SELECT_REQUEST_TYPE - def __init__(self, table_name, where): + def __init__(self, table_name, where, limit): self.namespace_no = table_name - if where: - (self.index_no, key) = where - self.key = [key] - else: + self.index_no = None + self.key_list = [] + if not where: 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.limit = 0xffffffff + self.limit = limit def pack(self): buf = ctypes.create_string_buffer(PACKET_BUF_LEN) @@ -260,8 +266,10 @@ class StatementSelect(StatementPing): self.index_no, self.offset, self.limit, - 1) - (buf, offset) = pack_tuple(self.key, buf, SELECT_REQUEST_FIXED_LEN) + len(self.key_list)) + offset = SELECT_REQUEST_FIXED_LEN + for key in self.key_list: + (buf, offset) = pack_tuple([key], buf, offset) return buf[:offset] diff --git a/test/lib/tarantool_silverbox_server.py b/test/lib/tarantool_silverbox_server.py index 0022714a82129d107c1f3138af53de9fd2886b8a..5d63e7e539d098b66bd2c57f85b9a6f44cd77f8b 100644 --- a/test/lib/tarantool_silverbox_server.py +++ b/test/lib/tarantool_silverbox_server.py @@ -33,7 +33,7 @@ def prepare_gdb(args): if term not in ["xterm", "rxvt", "urxvt", "gnome-terminal", "konsole"]: 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 diff --git a/test/lib/test_suite.py b/test/lib/test_suite.py index 6a4bec88eb164bbb2037aacb57f0f44fbb3b5558..1d82b3c18ae12c1c7c1755830cd37a36afcd0b0b 100644 --- a/test/lib/test_suite.py +++ b/test/lib/test_suite.py @@ -260,6 +260,8 @@ class TestSuite: def run_all(self): """For each file in the test suite, run client program assuming each file represents an individual test.""" + if len(self.tests) == 0: + return 0 server = TarantoolSilverboxServer(self.args, self.ini) server.install() server.start() diff --git a/test/test-run.py b/test/test-run.py index 78bd629ede6be2614d0d3d8460b516b1ce486003..d682c1755ef45ccd1e3016348f8d8b86d683927b 100755 --- a/test/test-run.py +++ b/test/test-run.py @@ -60,8 +60,9 @@ class Options: dest = 'suites', metavar = "suite", nargs="*", - default = ["box"], - help = """List of tests suites to look for tests in. Default: "box".""") + default = ["box", "box_big"], + help = """List of tests suites to look for tests in. Default: "box", + "box_big".""") parser.add_argument( "--force", @@ -156,7 +157,7 @@ def main(): suites.append(TestSuite(suite_name, options.args)) for suite in suites: - failed_tests = suite.run_all() + failed_tests += suite.run_all() except RuntimeError as e: print "\nFatal error: {0}. Execution aborted.".format(e) return (-1)