1# -*- coding: utf-8 -*-
2from tarantool.connection import Connection as BaseConnection
3from tarantool.error import *
4
5
6paramstyle = 'named'
7apilevel = "2.0"
8threadsafety = 1
9
10
11class Cursor:
12
13    def __init__(self, conn):
14        self._c = conn
15        self._lastrowid = None
16        self._rowcount = None
17        self.arraysize = 1
18        self._rows = None
19
20    def callproc(self, procname, *params):
21        """
22        Call a stored database procedure with the given name. The sequence of
23        parameters must contain one entry for each argument that the
24        procedure expects. The result of the call is returned as modified
25        copy of the input sequence. Input parameters are left untouched,
26        output and input/output parameters replaced with possibly new values.
27        """
28        raise NotSupportedError("callproc() method is not supported")
29
30    @property
31    def rows(self):
32        return self._rows
33
34    @property
35    def description(self):
36        # FIXME Implement this method please
37        raise NotImplementedError("description() property is not implemented")
38
39    def close(self):
40        """
41        Close the cursor now (rather than whenever __del__ is called).
42        The cursor will be unusable from this point forward; DatabaseError
43        exception will be raised if any operation is attempted with
44        the cursor.
45        """
46        self._c = None
47        self._rows = None
48        self._lastrowid = None
49        self._rowcount = None
50
51    def _check_not_closed(self, error=None):
52        if self._c is None:
53            raise InterfaceError(error or "Can not operate on a closed cursor")
54        if self._c.is_closed():
55            raise InterfaceError("The cursor can not be used "
56                                 "with a closed connection")
57
58    def execute(self, query, params=None):
59        """
60        Prepare and execute a database operation (query or command).
61        """
62        self._check_not_closed("Can not execute on closed cursor.")
63
64        response = self._c.execute(query, params)
65
66        self._rows = response.data
67        self._rowcount = response.affected_row_count or -1
68        if response.autoincrement_ids:
69            self._lastrowid = response.autoincrement_ids[-1]
70        else:
71            self._lastrowid = None
72
73    def executemany(self, query, param_sets):
74        self._check_not_closed("Can not execute on closed cursor.")
75        rowcount = 0
76        for params in param_sets:
77            self.execute(query, params)
78            if self.rowcount == -1:
79                rowcount = -1
80            if rowcount != -1:
81                rowcount += self.rowcount
82        self._rowcount = rowcount
83
84    @property
85    def lastrowid(self):
86        """
87        This read-only attribute provides the rowid of the last modified row
88        (most databases return a rowid only when a single INSERT operation is
89        performed).
90        """
91        return self._lastrowid
92
93    @property
94    def rowcount(self):
95        """
96        This read-only attribute specifies the number of rows that the last
97        .execute*() produced (for DQL statements like SELECT) or affected (
98        for DML statements like UPDATE or INSERT).
99        """
100        return self._rowcount
101
102    def _check_result_set(self, error=None):
103        """
104        Non-public method for raising an error when Cursor object does not have
105        any row to fetch. Useful for checking access after DQL requests.
106        """
107        if self._rows is None:
108            raise InterfaceError(error or "No result set to fetch from")
109
110    def fetchone(self):
111        """
112        Fetch the next row of a query result set, returning a single
113        sequence, or None when no more data is available.
114        """
115        self._check_result_set()
116        return self.fetchmany(1)[0] if self._rows else None
117
118    def fetchmany(self, size=None):
119        """
120        Fetch the next set of rows of a query result, returning a sequence of
121        sequences (e.g. a list of tuples). An empty sequence is returned when
122        no more rows are available.
123        """
124        self._check_result_set()
125
126        size = size or self.arraysize
127
128        if len(self._rows) < size:
129            items = self._rows
130            self._rows = []
131        else:
132            items, self._rows = self._rows[:size], self._rows[size:]
133
134        return items
135
136    def fetchall(self):
137        """Fetch all (remaining) rows of a query result, returning them as a
138        sequence of sequences (e.g. a list of tuples). Note that the cursor's
139        arraysize attribute can affect the performance of this operation.
140        """
141        self._check_result_set()
142
143        items = self._rows
144        self._rows = []
145        return items
146
147    def setinputsizes(self, sizes):
148        """PEP-249 allows to not implement this method and do nothing."""
149
150    def setoutputsize(self, size, column=None):
151        """PEP-249 allows to not implement this method and do nothing."""
152
153
154class Connection(BaseConnection):
155
156    def __init__(self, *args, **kwargs):
157        super(Connection, self).__init__(*args, **kwargs)
158        self._set_autocommit(kwargs.get('autocommit', True))
159
160    def _set_autocommit(self, autocommit):
161        """Autocommit is True by default and the default will be changed
162        to False. Set the autocommit property explicitly to True or verify
163        it when lean on autocommit behaviour."""
164        if not isinstance(autocommit, bool):
165            raise InterfaceError("autocommit parameter must be boolean, "
166                                 "not %s" % autocommit.__class__.__name__)
167        if autocommit is False:
168            raise NotSupportedError("The connector supports "
169                                    "only autocommit mode")
170        self._autocommit = autocommit
171
172    @property
173    def autocommit(self):
174        """Autocommit state"""
175        return self._autocommit
176
177    @autocommit.setter
178    def autocommit(self, autocommit):
179        """Set autocommit state"""
180        self._set_autocommit(autocommit)
181
182    def _check_not_closed(self, error=None):
183        """
184        Checks if the connection is not closed and rises an error if it is.
185        """
186        if self.is_closed():
187            raise InterfaceError(error or "The connector is closed")
188
189    def close(self):
190        """
191        Closes the connection
192        """
193        self._check_not_closed("The closed connector can not be closed again.")
194        super(Connection, self).close()
195
196    def commit(self):
197        """
198        Commit any pending transaction to the database.
199        """
200        self._check_not_closed("Can not commit on the closed connection")
201
202    def rollback(self):
203        """
204        Roll back pending transaction
205        """
206        self._check_not_closed("Can not roll back on a closed connection")
207        raise NotSupportedError("Transactions are not supported in this"
208                                "version of connector")
209
210    def cursor(self):
211        """
212        Return a new Cursor Object using the connection.
213        """
214        self._check_not_closed("Cursor creation is not allowed on a closed "
215                               "connection")
216        return Cursor(self)
217
218
219def connect(dsn=None, host=None, port=None,
220            user=None, password=None, **kwargs):
221    """
222    Constructor for creating a connection to the database.
223
224    :param str dsn: Data source name (Tarantool URI)
225                    ([[[username[:password]@]host:]port)
226    :param str host: Server hostname or IP-address
227    :param int port: Server port
228    :param str user: Tarantool user
229    :param str password: User password
230    :rtype: Connection
231    """
232
233    if dsn:
234        raise NotImplementedError("dsn param is not implemented in"
235                                  "this version of dbapi module")
236    params = {}
237    if host:
238        params["host"] = host
239    if port:
240        params["port"] = port
241    if user:
242        params["user"] = user
243    if password:
244        params["password"] = password
245
246    kwargs.update(params)
247
248    return Connection(**kwargs)
249