Skip to content

Commit 7c9238b

Browse files
committed
Add sql execute to connection
Closes #159 Co-authored-by Denis Ignatenko <denis.ignatenko@gamil.com>
1 parent b071e2e commit 7c9238b

File tree

4 files changed

+96
-6
lines changed

4 files changed

+96
-6
lines changed

tarantool/connection.py

+25-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
RequestSubscribe,
3535
RequestUpdate,
3636
RequestUpsert,
37-
RequestAuthenticate
37+
RequestAuthenticate,
38+
RequestExecute
3839
)
3940
from tarantool.space import Space
4041
from tarantool.const import (
@@ -250,17 +251,18 @@ def _read_response(self):
250251

251252
def _send_request_wo_reconnect(self, request):
252253
'''
253-
:rtype: `Response` instance
254+
:rtype: `Response` instance or subclass
254255
255256
:raise: NetworkError
256257
'''
257-
assert isinstance(request, Request)
258+
if not issubclass(type(request), Request):
259+
raise NetworkError
258260

259261
response = None
260262
while True:
261263
try:
262264
self._socket.sendall(bytes(request))
263-
response = Response(self, self._read_response())
265+
response = request.response_class(self, self._read_response())
264266
break
265267
except SchemaReloadException as e:
266268
self.update_schema(e.schema_version)
@@ -785,3 +787,22 @@ def generate_sync(self):
785787
Need override for async io connection
786788
'''
787789
return 0
790+
791+
def execute(self, query, params=None):
792+
'''
793+
Execute SQL request.
794+
795+
:param query: SQL syntax query
796+
:type query: str
797+
798+
:param params: Bind values to use in query
799+
:type params: list, dict
800+
801+
:return: query result data
802+
:rtype: list
803+
'''
804+
if not params:
805+
params = []
806+
request = RequestExecute(self, query, params)
807+
response = self._send_request(request)
808+
return response

tarantool/const.py

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929
#
3030
IPROTO_DATA = 0x30
3131
IPROTO_ERROR = 0x31
32+
#
33+
IPROTO_METADATA = 0x32
34+
IPROTO_SQL_TEXT = 0x40
35+
IPROTO_SQL_BIND = 0x41
36+
IPROTO_SQL_INFO = 0x42
37+
IPROTO_SQL_INFO_ROW_COUNT = 0x00
38+
IPROTO_SQL_INFO_AUTOINCREMENT_IDS = 0x01
3239

3340
IPROTO_GREETING_SIZE = 128
3441
IPROTO_BODY_MAX_LEN = 2147483648
@@ -44,6 +51,7 @@
4451
REQUEST_TYPE_EVAL = 8
4552
REQUEST_TYPE_UPSERT = 9
4653
REQUEST_TYPE_CALL = 10
54+
REQUEST_TYPE_EXECUTE = 11
4755
REQUEST_TYPE_PING = 64
4856
REQUEST_TYPE_JOIN = 65
4957
REQUEST_TYPE_SUBSCRIBE = 66

tarantool/request.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import msgpack
88
import hashlib
99

10-
10+
from tarantool.error import DatabaseError
1111
from tarantool.const import (
1212
IPROTO_CODE,
1313
IPROTO_SYNC,
@@ -27,6 +27,8 @@
2727
IPROTO_OPS,
2828
# IPROTO_INDEX_BASE,
2929
IPROTO_SCHEMA_ID,
30+
IPROTO_SQL_TEXT,
31+
IPROTO_SQL_BIND,
3032
REQUEST_TYPE_OK,
3133
REQUEST_TYPE_PING,
3234
REQUEST_TYPE_SELECT,
@@ -37,11 +39,13 @@
3739
REQUEST_TYPE_UPSERT,
3840
REQUEST_TYPE_CALL16,
3941
REQUEST_TYPE_CALL,
42+
REQUEST_TYPE_EXECUTE,
4043
REQUEST_TYPE_EVAL,
4144
REQUEST_TYPE_AUTHENTICATE,
4245
REQUEST_TYPE_JOIN,
4346
REQUEST_TYPE_SUBSCRIBE
4447
)
48+
from tarantool.response import Response, ResponseExecute
4549
from tarantool.utils import (
4650
strxor,
4751
binary_types
@@ -64,6 +68,7 @@ def __init__(self, conn):
6468
self.conn = conn
6569
self._sync = None
6670
self._body = ''
71+
self.response_class = Response
6772

6873
def __bytes__(self):
6974
return self.header(len(self._body)) + self._body
@@ -332,3 +337,24 @@ def __init__(self, conn, sync):
332337
request_body = msgpack.dumps({IPROTO_CODE: self.request_type,
333338
IPROTO_SYNC: sync})
334339
self._body = request_body
340+
341+
342+
class RequestExecute(Request):
343+
'''
344+
Represents EXECUTE request
345+
'''
346+
request_type = REQUEST_TYPE_EXECUTE
347+
348+
# pylint: disable=W0231
349+
def __init__(self, conn, sql, args):
350+
super(RequestExecute, self).__init__(conn)
351+
if isinstance(args, dict):
352+
args = [{":%s" % name: value} for name, value in args.items()]
353+
try:
354+
request_body = msgpack.dumps({IPROTO_SQL_TEXT: sql,
355+
IPROTO_SQL_BIND: args})
356+
except ValueError as e:
357+
raise DatabaseError("Value error: %s" % e)
358+
359+
self._body = request_body
360+
self.response_class = ResponseExecute

tarantool/response.py

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22
# pylint: disable=C0301,W0105,W0401,W0614
3+
from pprint import pprint
34

45
try:
56
# Python 3.3+
@@ -17,7 +18,10 @@
1718
IPROTO_ERROR,
1819
IPROTO_SYNC,
1920
IPROTO_SCHEMA_ID,
20-
REQUEST_TYPE_ERROR
21+
REQUEST_TYPE_ERROR,
22+
IPROTO_SQL_INFO,
23+
IPROTO_SQL_INFO_ROW_COUNT,
24+
IPROTO_SQL_INFO_AUTOINCREMENT_IDS
2125
)
2226
from tarantool.error import (
2327
DatabaseError,
@@ -248,3 +252,34 @@ def __str__(self):
248252
return ''.join(output)
249253

250254
__repr__ = __str__
255+
256+
257+
class ResponseExecute(Response):
258+
@property
259+
def lastrowid(self):
260+
if self.body is None:
261+
raise InterfaceError("Trying to access data, when there's no data")
262+
info = self.body.get(IPROTO_SQL_INFO)
263+
264+
if info is None:
265+
return None
266+
267+
lastrowids = info.get(IPROTO_SQL_INFO_AUTOINCREMENT_IDS)
268+
269+
return lastrowids[-1] if lastrowids else None
270+
271+
@property
272+
def rowcount(self):
273+
if self._body is None:
274+
raise InterfaceError("Trying to access data, when there's no data")
275+
276+
info = self._body.get(IPROTO_SQL_INFO)
277+
278+
if info is None:
279+
return -1
280+
281+
return info.get(IPROTO_SQL_INFO_ROW_COUNT, -1)
282+
283+
@property
284+
def rows(self):
285+
return self._data if self._body.get(IPROTO_DATA) is not None else None

0 commit comments

Comments
 (0)