From 932867650c97a4d0cf2c7121e338434b5b8c0ca4 Mon Sep 17 00:00:00 2001 From: Eugine Blikh <bigbes@gmail.com> Date: Fri, 13 Sep 2013 04:08:33 +0400 Subject: [PATCH] Add first support of python backend for TT. Still need to add support of error code (now it return 0 everytime) --- .gitmodules | 3 + test/lib/box_connection.py | 39 +++-- test/lib/sql_ast.py | 315 +++++++++---------------------------- test/lib/tarantool-python | 1 + 4 files changed, 100 insertions(+), 258 deletions(-) create mode 160000 test/lib/tarantool-python diff --git a/.gitmodules b/.gitmodules index 89aca4dd78..118dcc251b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "third_party/luajit"] path = third_party/luajit url = http://luajit.org/git/luajit-2.0.git +[submodule "test/lib/tarantool-python"] + path = test/lib/tarantool-python + url = https://github.com/mailru/tarantool-python.git diff --git a/test/lib/box_connection.py b/test/lib/box_connection.py index 2c160031f9..be93bbc60c 100644 --- a/test/lib/box_connection.py +++ b/test/lib/box_connection.py @@ -20,15 +20,28 @@ __author__ = "Konstantin Osipov <kostja.osipov@gmail.com>" # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. - -import socket +import os import sql +import sys +import socket import struct from tarantool_connection import TarantoolConnection +try: + tnt_py = os.path.dirname(os.path.abspath(__file__)) + tnt_py = os.path.join(tnt_py, 'tarantool-python/src') + sys.path.append(tnt_py) + from tarantool import Connection as tnt_connection +except ImportError: + raise + sys.stderr.write("\n\nNo tarantool-python library found\n") + sys.exit(1) + class BoxConnection(TarantoolConnection): def __init__(self, host, port): super(BoxConnection, self).__init__(host, port) + self.py_con = tnt_connection(host, port, connect_now=False) + self.py_con.error = False self.sort = False def recvall(self, length): @@ -47,27 +60,11 @@ class BoxConnection(TarantoolConnection): return "You have an error in your SQL syntax\n" statement.sort = self.sort - payload = statement.pack() - header = struct.pack("<lll", statement.reqeust_type, len(payload), 0) - - self.socket.sendall(header) - if len(payload): - self.socket.sendall(payload) - - IPROTO_HEADER_SIZE = 12 - - header = self.recvall(IPROTO_HEADER_SIZE) - - response_len = struct.unpack("<lll", header)[1] - - if response_len: - response = self.recvall(response_len) - else: - response = None + request = statement.pack(self.py_con) + response = self.py_con._send_request(request, False) if not silent: print command print statement.unpack(response) - return statement.unpack(response) + "\n" - + return statement.unpack(response) diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py index 700811017b..d7e054e570 100644 --- a/test/lib/sql_ast.py +++ b/test/lib/sql_ast.py @@ -1,28 +1,25 @@ -import struct +import os import re +import sys import ctypes +import struct -# IPROTO header is always 3 4-byte ints: -# command code, length, request id -INT_FIELD_LEN = 4 -INT_BER_MAX_LEN = 5 -IPROTO_HEADER_LEN = 3*INT_FIELD_LEN -INSERT_REQUEST_FIXED_LEN = 2*INT_FIELD_LEN -UPDATE_REQUEST_FIXED_LEN = 2*INT_FIELD_LEN -DELETE_REQUEST_FIXED_LEN = 2*INT_FIELD_LEN -SELECT_REQUEST_FIXED_LEN = 5*INT_FIELD_LEN -PACKET_BUF_LEN = 2048 - -UPDATE_SET_FIELD_OPCODE = 0 - -# command code in IPROTO header - -INSERT_REQUEST_TYPE = 13 -SELECT_REQUEST_TYPE = 17 -UPDATE_REQUEST_TYPE = 19 -DELETE_REQUEST_TYPE = 21 -CALL_REQUEST_TYPE = 22 -PING_REQUEST_TYPE = 65280 +try: + tnt_py = os.path.dirname(os.path.abspath(__file__)) + tnt_py = os.path.join(tnt_py, 'tarantool-python/src') + sys.path.append(tnt_py) + from tarantool.request import ( + RequestPing, + RequestInsert, + RequestSelect, + RequestCall, + RequestUpdate, + RequestDelete, + ) +except ImportError: + raise + sys.stderr.write("\n\nNo tarantool-python library found\n") + sys.exit(1) ER = { 0: "ER_OK" , @@ -85,231 +82,100 @@ ER = { 57: "ER_NO_SUCH_SPACE" } -def format_error(return_code, response): - return "An error occurred: {0}, '{1}'".format(ER[return_code >> 8], - response[4:-1]) - - -def save_varint32(value): - """Implement Perl pack's 'w' option, aka base 128 encoding.""" - res = '' - if value >= 1 << 7: - if value >= 1 << 14: - if value >= 1 << 21: - if value >= 1 << 28: - res += chr(value >> 28 & 0xff | 0x80) - res += chr(value >> 21 & 0xff | 0x80) - res += chr(value >> 14 & 0xff | 0x80) - res += chr(value >> 7 & 0xff | 0x80) - res += chr(value & 0x7F) - - return res - - -def read_varint32(varint, offset): - """Implement Perl unpack's 'w' option, aka base 128 decoding.""" - res = ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - if ord(varint[offset]) >= 0x80: - offset += 1 - res = ((res - 0x80) << 7) + ord(varint[offset]) - return res, offset + 1 - - -def opt_resize_buf(buf, newsize): - if len(buf) < newsize: - return ctypes.create_string_buffer(buf.value, max(2*len, newsize)) - return buf - - -def pack_field(value, buf, offset): - if type(value) is int or type(value) is long: - if value > 0xffffffff: - raise RuntimeError("Integer value is too big") - buf = opt_resize_buf(buf, offset + INT_FIELD_LEN) - struct.pack_into("<cL", buf, offset, chr(INT_FIELD_LEN), value) - offset += INT_FIELD_LEN + 1 - elif type(value) is str: - opt_resize_buf(buf, offset + INT_BER_MAX_LEN + len(value)) - value_len_ber = save_varint32(len(value)) - struct.pack_into("{0}s{1}s".format(len(value_len_ber), len(value)), - buf, offset, value_len_ber, value) - offset += len(value_len_ber) + len(value) - else: - raise RuntimeError("Unsupported value type in value list") - return (buf, offset) - - -def pack_tuple(value_list, buf, offset): - """Represents <tuple> rule in tarantool protocol description. - Pack tuple into a binary representation. - buf and offset are in-out parameters, offset is advanced - to the amount of bytes that it took to pack the tuple""" - - # length of int field: 1 byte - field len (is always 4), 4 bytes - data - # max length of compressed integer - cardinality = len(value_list) - struct.pack_into("<L", buf, offset, cardinality) - offset += INT_FIELD_LEN - for value in value_list: - (buf, offset) = pack_field(value, buf, offset) - - return buf, offset - - -def pack_operation_list(update_list, buf, offset): - buf = opt_resize_buf(buf, offset + INT_FIELD_LEN) - struct.pack_into("<L", buf, offset, len(update_list)) - offset += INT_FIELD_LEN - for update in update_list: - opt_resize_buf(buf, offset + INT_FIELD_LEN + 1) - struct.pack_into("<Lc", buf, offset, - update[0], - chr(UPDATE_SET_FIELD_OPCODE)) - offset += INT_FIELD_LEN + 1 - (buf, offset) = pack_field(update[1], buf, offset) - - return (buf, offset) - - -def unpack_tuple(response, offset): - (size, cardinality) = struct.unpack("<LL", response[offset:offset + 8]) - offset += 8 - res = [] - while len(res) < cardinality: - (data_len, offset) = read_varint32(response, offset) - data = response[offset:offset+data_len] - offset += data_len - if data_len == 4: - (data,) = struct.unpack("<L", data) - res.append((str(data))) - else: - res.append("'" + data + "'") - - return '[' + ', '.join(res) + ']', offset +def format_error(response): + return "An error occurred: {0}".format(ER[response.return_code >> 8]) +class Statement(object): + def __init__(self): + pass + def pack(self, connection): + pass + def unpack(self, response): + pass -class StatementPing: - reqeust_type = PING_REQUEST_TYPE - def pack(self): - return "" +class StatementPing(Statement): + def pack(self, connection): + return RequestPing(connection) def unpack(self, response): + if response._return_code: + return format_error(response) return "ok\n---" -class StatementInsert(StatementPing): - reqeust_type = INSERT_REQUEST_TYPE - +class StatementInsert(Statement): def __init__(self, table_name, value_list): self.space_no = table_name - self.flags = 0x02 # ADD + self.flags = 0x03 # ADD self.value_list = value_list - def pack(self): - buf = ctypes.create_string_buffer(PACKET_BUF_LEN) - (buf, offset) = pack_tuple(self.value_list, buf, INSERT_REQUEST_FIXED_LEN) - struct.pack_into("<LL", buf, 0, self.space_no, self.flags) - return buf[:offset] + def pack(self, connection): + return RequestInsert(connection, self.space_no, self.value_list, self.flags) def unpack(self, response): - (return_code,) = struct.unpack("<L", response[:4]) - if return_code: - return format_error(return_code, response) - (tuple_count,) = struct.unpack("<L", response[4:8]) - return "Insert OK, {0} row affected".format(tuple_count) - -class StatementReplace(StatementPing): - reqeust_type = INSERT_REQUEST_TYPE + if response._return_code: + return format_error(response) + return "Insert OK, {0} row affected".format(len(response)) +class StatementReplace(Statement): def __init__(self, table_name, value_list): self.space_no = table_name - self.flags = 0x04 # REPLACE + self.flags = 0x05 # REPLACE self.value_list = value_list - def pack(self): - buf = ctypes.create_string_buffer(PACKET_BUF_LEN) - (buf, offset) = pack_tuple(self.value_list, buf, INSERT_REQUEST_FIXED_LEN) - struct.pack_into("<LL", buf, 0, self.space_no, self.flags) - return buf[:offset] + def pack(self, connection): + return RequestInsert(connection, self.space_no, self.value_list, self.flags) def unpack(self, response): - (return_code,) = struct.unpack("<L", response[:4]) - if return_code: - return format_error(return_code, response) - (tuple_count,) = struct.unpack("<L", response[4:8]) - return "Replace OK, {0} row affected".format(tuple_count) - -class StatementUpdate(StatementPing): - reqeust_type = UPDATE_REQUEST_TYPE + if response._return_code: + return format_error(response) + return "Insert OK, {0} row affected".format(len(response)) +class StatementUpdate(Statement): def __init__(self, table_name, update_list, where): self.space_no = table_name self.flags = 0 key_no = where[0] if key_no != 0: raise RuntimeError("UPDATE can only be made by the primary key (#0)") - self.value_list = where[1:] - self.update_list = update_list + self.value_list = where[1] + self.update_list = [(pair[0], '=', pair[1]) for pair in update_list] - def pack(self): - buf = ctypes.create_string_buffer(PACKET_BUF_LEN) - struct.pack_into("<LL", buf, 0, self.space_no, self.flags) - (buf, offset) = pack_tuple(self.value_list, buf, UPDATE_REQUEST_FIXED_LEN) - (buf, offset) = pack_operation_list(self.update_list, buf, offset) - return buf[:offset] + def pack(self, connection): + return RequestUpdate(connection, self.space_no, self.value_list, True) def unpack(self, response): - (return_code,) = struct.unpack("<L", response[:4]) - if return_code: - return format_error(return_code, response) - (tuple_count,) = struct.unpack("<L", response[4:8]) - return "Update OK, {0} row affected".format(tuple_count) - -class StatementDelete(StatementPing): - reqeust_type = DELETE_REQUEST_TYPE + if response._return_code: + return format_error(response) + return "Update OK, {0} row affected".format(len(response)) +class StatementDelete(Statement): def __init__(self, table_name, where): self.space_no = table_name self.flags = 0 key_no = where[0] if key_no != 0: raise RuntimeError("DELETE can only be made by the primary key (#0)") - self.value_list = where[1:] + self.value_list = where[1] - def pack(self): - buf = ctypes.create_string_buffer(PACKET_BUF_LEN) - (buf, offset) = pack_tuple(self.value_list, buf, DELETE_REQUEST_FIXED_LEN) - struct.pack_into("<LL", buf, 0, self.space_no, self.flags) - return buf[:offset] + def pack(self, connection): + return RequestDelete(connection, self.space_no, self.value_list, True) def unpack(self, response): - (return_code,) = struct.unpack("<L", response[:4]) - if return_code: - return format_error(return_code, response) - (tuple_count,) = struct.unpack("<L", response[4:8]) - return "Delete OK, {0} row affected".format(tuple_count) - -class StatementSelect(StatementPing): - reqeust_type = SELECT_REQUEST_TYPE + if response._return_code: + return format_error(response) + return "Delete OK, {0} row affected".format(len(response)) +class StatementSelect(Statement): def __init__(self, table_name, where, limit): self.space_no = table_name self.index_no = None self.key_list = [] if not where: self.index_no = 0 - self.key_list = ["",] + self.key_list = [[]] else: for (index_no, key) in where: - self.key_list.append(key) + self.key_list.append([key, ]) if self.index_no == None: self.index_no = index_no elif self.index_no != index_no: @@ -317,54 +183,29 @@ class StatementSelect(StatementPing): self.offset = 0 self.limit = limit - def pack(self): - buf = ctypes.create_string_buffer(PACKET_BUF_LEN) - struct.pack_into("<LLLLL", buf, 0, - self.space_no, - self.index_no, - self.offset, - self.limit, - 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] + def pack(self, connection): + return RequestSelect(connection, self.space_no, self.index_no, + self.key_list , self.offset, self.limit) def unpack(self, response): - (return_code,) = struct.unpack("<L", response[:4]) - if return_code: - return format_error(return_code, response) - (tuple_count,) = struct.unpack("<L", response[4:8]) - tuples = [] - offset = 8 - while len(tuples) < tuple_count: - (next_tuple, offset) = unpack_tuple(response, offset) - tuples.append(next_tuple) + if response._return_code: + return format_error(response) if self.sort: - tuples.sort() - if tuple_count == 0: + response = sorted(response[0:]) + if not len(response): return "No match" - elif tuple_count == 1: - return "Found 1 tuple:\n" + tuples[0] + elif len(response) == 1: + return "Found 1 tuple:\n" + str(response[0]) else: - return "Found {0} tuples:\n".format(tuple_count) + "\n".join(tuples) + return "Found {0} tuples:\n".format(len(response)) + \ + "\n".join([str(tup) for tup in response]) class StatementCall(StatementSelect): - reqeust_type = CALL_REQUEST_TYPE - def __init__(self, proc_name, value_list): self.proc_name = proc_name # the binary protocol passes everything into procedure as strings # convert input to strings to avoid data mangling by the protocol self.value_list = map(lambda val: str(val), value_list) - def pack(self): - buf = ctypes.create_string_buffer(PACKET_BUF_LEN) - offset = 0 - struct.pack_into("<L", buf, offset, 0) # flags - offset += INT_FIELD_LEN - (buf, offset) = pack_field(self.proc_name, buf, offset) - (buf, offset) = pack_tuple(self.value_list, buf, offset) - return buf[:offset] + def pack(self, connection): + return RequestCall(connection, self.proc_name, self.value_list, True) diff --git a/test/lib/tarantool-python b/test/lib/tarantool-python new file mode 160000 index 0000000000..e3e3f6f284 --- /dev/null +++ b/test/lib/tarantool-python @@ -0,0 +1 @@ +Subproject commit e3e3f6f28429900850ed7af7b9b481002e1b1894 -- GitLab