singlestoredb 0.4.0__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- singlestoredb/__init__.py +33 -1
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/auth.py +5 -1
- singlestoredb/config.py +116 -14
- singlestoredb/connection.py +483 -516
- singlestoredb/converters.py +238 -135
- singlestoredb/exceptions.py +30 -2
- singlestoredb/functions/__init__.py +1 -0
- singlestoredb/functions/decorator.py +142 -0
- singlestoredb/functions/dtypes.py +1639 -0
- singlestoredb/functions/ext/__init__.py +2 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +661 -0
- singlestoredb/functions/ext/json.py +427 -0
- singlestoredb/functions/ext/mmap.py +306 -0
- singlestoredb/functions/ext/rowdat_1.py +744 -0
- singlestoredb/functions/signature.py +673 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +621 -0
- singlestoredb/fusion/handlers/stage.py +257 -0
- singlestoredb/fusion/handlers/utils.py +162 -0
- singlestoredb/fusion/handlers/workspace.py +412 -0
- singlestoredb/fusion/registry.py +164 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/{http.py → http/connection.py} +555 -154
- singlestoredb/management/__init__.py +3 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +14 -6
- singlestoredb/management/manager.py +100 -38
- singlestoredb/management/organization.py +188 -0
- singlestoredb/management/region.py +5 -5
- singlestoredb/management/utils.py +281 -2
- singlestoredb/management/workspace.py +1344 -49
- singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
- singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
- singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
- singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
- singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
- singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
- singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
- singlestoredb/pytest.py +283 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +385 -0
- singlestoredb/tests/test.sql +210 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +482 -115
- singlestoredb/tests/test_config.py +13 -13
- singlestoredb/tests/test_connection.py +241 -305
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_ext_func.py +1193 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +465 -0
- singlestoredb/tests/test_http.py +32 -26
- singlestoredb/tests/test_management.py +588 -8
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +11 -12
- singlestoredb/tests/test_udf.py +687 -0
- singlestoredb/tests/utils.py +3 -2
- singlestoredb/utils/config.py +58 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +4 -1
- singlestoredb-1.0.4.dist-info/METADATA +139 -0
- singlestoredb-1.0.4.dist-info/RECORD +112 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
- singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
- singlestoredb/clients/pymysqlsv/converters.py +0 -365
- singlestoredb/clients/pymysqlsv/err.py +0 -144
- singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
- singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
- singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
- singlestoredb/drivers/__init__.py +0 -45
- singlestoredb/drivers/base.py +0 -198
- singlestoredb/drivers/cymysql.py +0 -38
- singlestoredb/drivers/http.py +0 -47
- singlestoredb/drivers/mariadb.py +0 -40
- singlestoredb/drivers/mysqlconnector.py +0 -49
- singlestoredb/drivers/mysqldb.py +0 -60
- singlestoredb/drivers/pymysql.py +0 -37
- singlestoredb/drivers/pymysqlsv.py +0 -35
- singlestoredb/drivers/pyodbc.py +0 -65
- singlestoredb-0.4.0.dist-info/METADATA +0 -111
- singlestoredb-0.4.0.dist-info/RECORD +0 -86
- /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
- /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# type: ignore
|
|
2
2
|
import re
|
|
3
|
+
from collections import namedtuple
|
|
3
4
|
|
|
4
5
|
from . import err
|
|
6
|
+
from ..connection import Cursor as BaseCursor
|
|
7
|
+
from ..utils.debug import log_query
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
#: Regular expression for :meth:`Cursor.executemany`.
|
|
@@ -15,15 +18,21 @@ RE_INSERT_VALUES = re.compile(
|
|
|
15
18
|
)
|
|
16
19
|
|
|
17
20
|
|
|
18
|
-
class Cursor:
|
|
21
|
+
class Cursor(BaseCursor):
|
|
19
22
|
"""
|
|
20
23
|
This is the object used to interact with the database.
|
|
21
24
|
|
|
22
25
|
Do not create an instance of a Cursor yourself. Call
|
|
23
|
-
|
|
26
|
+
connection.Connection.cursor().
|
|
24
27
|
|
|
25
28
|
See `Cursor <https://www.python.org/dev/peps/pep-0249/#cursor-objects>`_ in
|
|
26
29
|
the specification.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
connection : Connection
|
|
34
|
+
The connection the cursor is associated with.
|
|
35
|
+
|
|
27
36
|
"""
|
|
28
37
|
|
|
29
38
|
#: Max statement size which :meth:`executemany` generates.
|
|
@@ -33,27 +42,54 @@ class Cursor:
|
|
|
33
42
|
max_stmt_length = 1024000
|
|
34
43
|
|
|
35
44
|
def __init__(self, connection):
|
|
36
|
-
self.
|
|
37
|
-
self.
|
|
38
|
-
self.
|
|
45
|
+
self._connection = connection
|
|
46
|
+
self.warning_count = 0
|
|
47
|
+
self._description = None
|
|
48
|
+
self._rownumber = 0
|
|
39
49
|
self.rowcount = -1
|
|
40
50
|
self.arraysize = 1
|
|
41
51
|
self._executed = None
|
|
42
52
|
self._result = None
|
|
43
53
|
self._rows = None
|
|
54
|
+
self.lastrowid = None
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def messages(self):
|
|
58
|
+
# TODO
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def description(self):
|
|
63
|
+
return self._description
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def connection(self):
|
|
67
|
+
return self._connection
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def rownumber(self):
|
|
71
|
+
return self._rownumber
|
|
44
72
|
|
|
45
73
|
def close(self):
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
"""
|
|
49
|
-
conn = self.connection
|
|
74
|
+
"""Closing a cursor just exhausts all remaining data."""
|
|
75
|
+
conn = self._connection
|
|
50
76
|
if conn is None:
|
|
51
77
|
return
|
|
52
78
|
try:
|
|
53
79
|
while self.nextset():
|
|
54
80
|
pass
|
|
55
81
|
finally:
|
|
56
|
-
self.
|
|
82
|
+
self._connection = None
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def open(self) -> bool:
|
|
86
|
+
conn = self._connection
|
|
87
|
+
if conn is None:
|
|
88
|
+
return False
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
def is_connected(self):
|
|
92
|
+
return self.open
|
|
57
93
|
|
|
58
94
|
def __enter__(self):
|
|
59
95
|
return self
|
|
@@ -63,9 +99,9 @@ class Cursor:
|
|
|
63
99
|
self.close()
|
|
64
100
|
|
|
65
101
|
def _get_db(self):
|
|
66
|
-
if not self.
|
|
102
|
+
if not self._connection:
|
|
67
103
|
raise err.ProgrammingError('Cursor closed')
|
|
68
|
-
return self.
|
|
104
|
+
return self._connection
|
|
69
105
|
|
|
70
106
|
def _check_executed(self):
|
|
71
107
|
if not self._executed:
|
|
@@ -99,87 +135,94 @@ class Cursor:
|
|
|
99
135
|
def nextset(self):
|
|
100
136
|
return self._nextset(False)
|
|
101
137
|
|
|
102
|
-
def _ensure_bytes(self, x, encoding=None):
|
|
103
|
-
if isinstance(x, str):
|
|
104
|
-
x = x.encode(encoding)
|
|
105
|
-
elif isinstance(x, (tuple, list)):
|
|
106
|
-
x = type(x)(self._ensure_bytes(v, encoding=encoding) for v in x)
|
|
107
|
-
return x
|
|
108
|
-
|
|
109
138
|
def _escape_args(self, args, conn):
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
139
|
+
dtype = type(args)
|
|
140
|
+
literal = conn.literal
|
|
141
|
+
if dtype is tuple or dtype is list or isinstance(args, (tuple, list)):
|
|
142
|
+
return tuple(literal(arg) for arg in args)
|
|
143
|
+
elif dtype is dict or isinstance(args, dict):
|
|
144
|
+
return {key: literal(val) for (key, val) in args.items()}
|
|
145
|
+
# If it's not a dictionary let's try escaping it anyways.
|
|
146
|
+
# Worst case it will throw a Value error
|
|
147
|
+
return conn.escape(args)
|
|
118
148
|
|
|
119
149
|
def mogrify(self, query, args=None):
|
|
120
150
|
"""
|
|
121
|
-
Returns the exact string
|
|
122
|
-
execute() method.
|
|
151
|
+
Returns the exact string sent to the database by calling the execute() method.
|
|
123
152
|
|
|
124
|
-
|
|
125
|
-
:type query: str
|
|
153
|
+
This method follows the extension to the DB API 2.0 followed by Psycopg.
|
|
126
154
|
|
|
127
|
-
|
|
128
|
-
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
query : str
|
|
158
|
+
Query to mogrify.
|
|
159
|
+
args : Sequence[Any] or Dict[str, Any] or Any, optional
|
|
160
|
+
Parameters used with query. (optional)
|
|
129
161
|
|
|
130
|
-
|
|
131
|
-
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
str : The query with argument binding applied.
|
|
132
165
|
|
|
133
|
-
This method follows the extension to the DB API 2.0 followed by Psycopg.
|
|
134
166
|
"""
|
|
135
167
|
conn = self._get_db()
|
|
136
168
|
|
|
137
|
-
if args
|
|
169
|
+
if args:
|
|
138
170
|
query = query % self._escape_args(args, conn)
|
|
139
171
|
|
|
140
172
|
return query
|
|
141
173
|
|
|
142
174
|
def execute(self, query, args=None):
|
|
143
|
-
"""
|
|
175
|
+
"""
|
|
176
|
+
Execute a query.
|
|
144
177
|
|
|
145
|
-
:
|
|
146
|
-
|
|
178
|
+
If args is a list or tuple, :1, :2, etc. can be used as a
|
|
179
|
+
placeholder in the query. If args is a dict, :name can be used
|
|
180
|
+
as a placeholder in the query.
|
|
147
181
|
|
|
148
|
-
|
|
149
|
-
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
query : str
|
|
185
|
+
Query to execute.
|
|
186
|
+
args : Sequence[Any] or Dict[str, Any] or Any, optional
|
|
187
|
+
Parameters used with query. (optional)
|
|
150
188
|
|
|
151
|
-
|
|
152
|
-
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
int : Number of affected rows.
|
|
153
192
|
|
|
154
|
-
If args is a list or tuple, %s can be used as a placeholder in the query.
|
|
155
|
-
If args is a dict, %(name)s can be used as a placeholder in the query.
|
|
156
193
|
"""
|
|
157
194
|
while self.nextset():
|
|
158
195
|
pass
|
|
159
196
|
|
|
197
|
+
log_query(query, args)
|
|
198
|
+
|
|
160
199
|
query = self.mogrify(query, args)
|
|
161
200
|
|
|
162
201
|
result = self._query(query)
|
|
163
202
|
self._executed = query
|
|
164
203
|
return result
|
|
165
204
|
|
|
166
|
-
def executemany(self, query, args):
|
|
167
|
-
"""
|
|
168
|
-
|
|
169
|
-
:param query: Query to execute.
|
|
170
|
-
:type query: str
|
|
171
|
-
|
|
172
|
-
:param args: Sequence of sequences or mappings. It is used as parameter.
|
|
173
|
-
:type args: tuple or list
|
|
174
|
-
|
|
175
|
-
:return: Number of rows affected, if any.
|
|
176
|
-
:rtype: int or None
|
|
205
|
+
def executemany(self, query, args=None):
|
|
206
|
+
"""
|
|
207
|
+
Run several data against one query.
|
|
177
208
|
|
|
178
209
|
This method improves performance on multiple-row INSERT and
|
|
179
210
|
REPLACE. Otherwise it is equivalent to looping over args with
|
|
180
211
|
execute().
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
query : str,
|
|
216
|
+
Query to execute.
|
|
217
|
+
args : Sequnce[Any], optional
|
|
218
|
+
Sequence of sequences or mappings. It is used as parameter.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
int : Number of rows affected, if any.
|
|
223
|
+
|
|
181
224
|
"""
|
|
182
|
-
if
|
|
225
|
+
if args is None or len(args) == 0:
|
|
183
226
|
return
|
|
184
227
|
|
|
185
228
|
m = RE_INSERT_VALUES.match(query)
|
|
@@ -210,7 +253,11 @@ class Cursor:
|
|
|
210
253
|
if isinstance(postfix, str):
|
|
211
254
|
postfix = postfix.encode(encoding)
|
|
212
255
|
sql = bytearray(prefix)
|
|
213
|
-
|
|
256
|
+
# Detect dataframes
|
|
257
|
+
if hasattr(args, 'itertuples'):
|
|
258
|
+
args = args.itertuples(index=False)
|
|
259
|
+
else:
|
|
260
|
+
args = iter(args)
|
|
214
261
|
v = values % escape(next(args), conn)
|
|
215
262
|
if isinstance(v, str):
|
|
216
263
|
v = v.encode(encoding, 'surrogateescape')
|
|
@@ -218,7 +265,7 @@ class Cursor:
|
|
|
218
265
|
rows = 0
|
|
219
266
|
for arg in args:
|
|
220
267
|
v = values % escape(arg, conn)
|
|
221
|
-
if isinstance(v, str):
|
|
268
|
+
if type(v) is str or isinstance(v, str):
|
|
222
269
|
v = v.encode(encoding, 'surrogateescape')
|
|
223
270
|
if len(sql) + len(v) + len(postfix) + 1 > max_stmt_length:
|
|
224
271
|
rows += self.execute(sql + postfix)
|
|
@@ -231,15 +278,8 @@ class Cursor:
|
|
|
231
278
|
return rows
|
|
232
279
|
|
|
233
280
|
def callproc(self, procname, args=()):
|
|
234
|
-
"""
|
|
235
|
-
|
|
236
|
-
:param procname: Name of procedure to execute on server.
|
|
237
|
-
:type procname: str
|
|
238
|
-
|
|
239
|
-
:param args: Sequence of parameters to use with procedure.
|
|
240
|
-
:type args: tuple or list
|
|
241
|
-
|
|
242
|
-
Returns the original args.
|
|
281
|
+
"""
|
|
282
|
+
Execute stored procedure procname with args.
|
|
243
283
|
|
|
244
284
|
Compatibility warning: PEP-249 specifies that any modified
|
|
245
285
|
parameters must be returned. This is currently impossible
|
|
@@ -259,6 +299,18 @@ class Cursor:
|
|
|
259
299
|
behavior with respect to the DB-API. Be sure to use nextset()
|
|
260
300
|
to advance through all result sets; otherwise you may get
|
|
261
301
|
disconnected.
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
procname : str
|
|
306
|
+
Name of procedure to execute on server.
|
|
307
|
+
args : Sequence[Any], optional
|
|
308
|
+
Sequence of parameters to use with procedure.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
Sequence[Any] : The original args.
|
|
313
|
+
|
|
262
314
|
"""
|
|
263
315
|
conn = self._get_db()
|
|
264
316
|
if args:
|
|
@@ -271,7 +323,7 @@ class Cursor:
|
|
|
271
323
|
)
|
|
272
324
|
self.nextset()
|
|
273
325
|
|
|
274
|
-
q = 'CALL
|
|
326
|
+
q = 'CALL {}({})'.format(
|
|
275
327
|
procname,
|
|
276
328
|
','.join(['@_%s_%d' % (procname, i) for i in range(len(args))]),
|
|
277
329
|
)
|
|
@@ -286,20 +338,21 @@ class Cursor:
|
|
|
286
338
|
|
|
287
339
|
def _unchecked_fetchone(self):
|
|
288
340
|
"""Fetch the next row."""
|
|
289
|
-
if self._rows is None or self.
|
|
341
|
+
if self._rows is None or self._rownumber >= len(self._rows):
|
|
290
342
|
return None
|
|
291
|
-
result = self._rows[self.
|
|
292
|
-
self.
|
|
343
|
+
result = self._rows[self._rownumber]
|
|
344
|
+
self._rownumber += 1
|
|
293
345
|
return result
|
|
294
346
|
|
|
295
347
|
def fetchmany(self, size=None):
|
|
296
348
|
"""Fetch several rows."""
|
|
297
349
|
self._check_executed()
|
|
298
350
|
if self._rows is None:
|
|
351
|
+
self.warning_count = self._result.warning_count
|
|
299
352
|
return ()
|
|
300
|
-
end = self.
|
|
301
|
-
result = self._rows[self.
|
|
302
|
-
self.
|
|
353
|
+
end = self._rownumber + (size or self.arraysize)
|
|
354
|
+
result = self._rows[self._rownumber: end]
|
|
355
|
+
self._rownumber = min(end, len(self._rows))
|
|
303
356
|
return result
|
|
304
357
|
|
|
305
358
|
def fetchall(self):
|
|
@@ -307,17 +360,17 @@ class Cursor:
|
|
|
307
360
|
self._check_executed()
|
|
308
361
|
if self._rows is None:
|
|
309
362
|
return ()
|
|
310
|
-
if self.
|
|
311
|
-
result = self._rows[self.
|
|
363
|
+
if self._rownumber:
|
|
364
|
+
result = self._rows[self._rownumber:]
|
|
312
365
|
else:
|
|
313
366
|
result = self._rows
|
|
314
|
-
self.
|
|
367
|
+
self._rownumber = len(self._rows)
|
|
315
368
|
return result
|
|
316
369
|
|
|
317
370
|
def scroll(self, value, mode='relative'):
|
|
318
371
|
self._check_executed()
|
|
319
372
|
if mode == 'relative':
|
|
320
|
-
r = self.
|
|
373
|
+
r = self._rownumber + value
|
|
321
374
|
elif mode == 'absolute':
|
|
322
375
|
r = value
|
|
323
376
|
else:
|
|
@@ -325,7 +378,7 @@ class Cursor:
|
|
|
325
378
|
|
|
326
379
|
if not (0 <= r < len(self._rows)):
|
|
327
380
|
raise IndexError('out of range')
|
|
328
|
-
self.
|
|
381
|
+
self._rownumber = r
|
|
329
382
|
|
|
330
383
|
def _query(self, q):
|
|
331
384
|
conn = self._get_db()
|
|
@@ -335,11 +388,12 @@ class Cursor:
|
|
|
335
388
|
return self.rowcount
|
|
336
389
|
|
|
337
390
|
def _clear_result(self):
|
|
338
|
-
self.
|
|
391
|
+
self._rownumber = 0
|
|
339
392
|
self._result = None
|
|
340
393
|
|
|
341
394
|
self.rowcount = 0
|
|
342
|
-
self.
|
|
395
|
+
self.warning_count = 0
|
|
396
|
+
self._description = None
|
|
343
397
|
self.lastrowid = None
|
|
344
398
|
self._rows = None
|
|
345
399
|
|
|
@@ -349,15 +403,19 @@ class Cursor:
|
|
|
349
403
|
self._result = result = conn._result
|
|
350
404
|
|
|
351
405
|
self.rowcount = result.affected_rows
|
|
352
|
-
self.
|
|
406
|
+
self.warning_count = result.warning_count
|
|
407
|
+
# Affected rows is set to max int64 for compatibility with MySQLdb, but
|
|
408
|
+
# the DB-API requires this value to be -1. This happens in unbuffered mode.
|
|
409
|
+
if self.rowcount == 18446744073709551615:
|
|
410
|
+
self.rowcount = -1
|
|
411
|
+
self._description = result.description
|
|
353
412
|
self.lastrowid = result.insert_id
|
|
354
413
|
self._rows = result.rows
|
|
355
414
|
|
|
356
415
|
def __iter__(self):
|
|
357
416
|
self._check_executed()
|
|
358
|
-
_unchecked_fetchone = self._unchecked_fetchone
|
|
359
417
|
|
|
360
|
-
def fetchall_unbuffered_gen():
|
|
418
|
+
def fetchall_unbuffered_gen(_unchecked_fetchone=self._unchecked_fetchone):
|
|
361
419
|
while True:
|
|
362
420
|
out = _unchecked_fetchone()
|
|
363
421
|
if out is not None:
|
|
@@ -378,6 +436,10 @@ class Cursor:
|
|
|
378
436
|
NotSupportedError = err.NotSupportedError
|
|
379
437
|
|
|
380
438
|
|
|
439
|
+
class CursorSV(Cursor):
|
|
440
|
+
"""Cursor class for C extension."""
|
|
441
|
+
|
|
442
|
+
|
|
381
443
|
class DictCursorMixin:
|
|
382
444
|
# You can override this to use OrderedDict or other dict-like types.
|
|
383
445
|
dict_type = dict
|
|
@@ -385,7 +447,7 @@ class DictCursorMixin:
|
|
|
385
447
|
def _do_get_result(self):
|
|
386
448
|
super(DictCursorMixin, self)._do_get_result()
|
|
387
449
|
fields = []
|
|
388
|
-
if self.
|
|
450
|
+
if self._description:
|
|
389
451
|
for f in self._result.fields:
|
|
390
452
|
name = f.name
|
|
391
453
|
if name in fields:
|
|
@@ -403,7 +465,42 @@ class DictCursorMixin:
|
|
|
403
465
|
|
|
404
466
|
|
|
405
467
|
class DictCursor(DictCursorMixin, Cursor):
|
|
406
|
-
"""A cursor which returns results as a dictionary"""
|
|
468
|
+
"""A cursor which returns results as a dictionary."""
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
class DictCursorSV(Cursor):
|
|
472
|
+
"""A cursor which returns results as a dictionary for C extension."""
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class NamedtupleCursorMixin:
|
|
476
|
+
|
|
477
|
+
def _do_get_result(self):
|
|
478
|
+
super(NamedtupleCursorMixin, self)._do_get_result()
|
|
479
|
+
fields = []
|
|
480
|
+
if self._description:
|
|
481
|
+
for f in self._result.fields:
|
|
482
|
+
name = f.name
|
|
483
|
+
if name in fields:
|
|
484
|
+
name = f.table_name + '.' + name
|
|
485
|
+
fields.append(name)
|
|
486
|
+
self._fields = fields
|
|
487
|
+
self._namedtuple = namedtuple('Row', self._fields, rename=True)
|
|
488
|
+
|
|
489
|
+
if fields and self._rows:
|
|
490
|
+
self._rows = [self._conv_row(r) for r in self._rows]
|
|
491
|
+
|
|
492
|
+
def _conv_row(self, row):
|
|
493
|
+
if row is None:
|
|
494
|
+
return None
|
|
495
|
+
return self._namedtuple(*row)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class NamedtupleCursor(NamedtupleCursorMixin, Cursor):
|
|
499
|
+
"""A cursor which returns results in a named tuple."""
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class NamedtupleCursorSV(Cursor):
|
|
503
|
+
"""A cursor which returns results as a named tuple for C extension."""
|
|
407
504
|
|
|
408
505
|
|
|
409
506
|
class SSCursor(Cursor):
|
|
@@ -420,13 +517,14 @@ class SSCursor(Cursor):
|
|
|
420
517
|
returning the total number of rows, so the only way to tell how many rows
|
|
421
518
|
there are is to iterate over every row returned. Also, it currently isn't
|
|
422
519
|
possible to scroll backwards, as only the current row is held in memory.
|
|
520
|
+
|
|
423
521
|
"""
|
|
424
522
|
|
|
425
523
|
def _conv_row(self, row):
|
|
426
524
|
return row
|
|
427
525
|
|
|
428
526
|
def close(self):
|
|
429
|
-
conn = self.
|
|
527
|
+
conn = self._connection
|
|
430
528
|
if conn is None:
|
|
431
529
|
return
|
|
432
530
|
|
|
@@ -437,7 +535,7 @@ class SSCursor(Cursor):
|
|
|
437
535
|
while self.nextset():
|
|
438
536
|
pass
|
|
439
537
|
finally:
|
|
440
|
-
self.
|
|
538
|
+
self._connection = None
|
|
441
539
|
|
|
442
540
|
__del__ = close
|
|
443
541
|
|
|
@@ -464,28 +562,34 @@ class SSCursor(Cursor):
|
|
|
464
562
|
"""Fetch next row."""
|
|
465
563
|
row = self.read_next()
|
|
466
564
|
if row is None:
|
|
565
|
+
self.warning_count = self._result.warning_count
|
|
467
566
|
return None
|
|
468
|
-
self.
|
|
567
|
+
self._rownumber += 1
|
|
469
568
|
return row
|
|
470
569
|
|
|
471
570
|
def fetchall(self):
|
|
472
571
|
"""
|
|
473
|
-
Fetch all, as per MySQLdb.
|
|
474
|
-
|
|
572
|
+
Fetch all, as per MySQLdb.
|
|
573
|
+
|
|
574
|
+
Pretty useless for large queries, as it is buffered.
|
|
575
|
+
See fetchall_unbuffered(), if you want an unbuffered
|
|
475
576
|
generator version of this method.
|
|
577
|
+
|
|
476
578
|
"""
|
|
477
579
|
return list(self.fetchall_unbuffered())
|
|
478
580
|
|
|
479
581
|
def fetchall_unbuffered(self):
|
|
480
582
|
"""
|
|
481
|
-
Fetch all, implemented as a generator
|
|
482
|
-
|
|
483
|
-
|
|
583
|
+
Fetch all, implemented as a generator.
|
|
584
|
+
|
|
585
|
+
This is not a standard DB-API operation, however, it doesn't make
|
|
586
|
+
sense to return everything in a list, as that would use ridiculous
|
|
587
|
+
memory for large result sets.
|
|
588
|
+
|
|
484
589
|
"""
|
|
485
590
|
self._check_executed()
|
|
486
|
-
_unchecked_fetchone = self._unchecked_fetchone
|
|
487
591
|
|
|
488
|
-
def fetchall_unbuffered_gen():
|
|
592
|
+
def fetchall_unbuffered_gen(_unchecked_fetchone=self._unchecked_fetchone):
|
|
489
593
|
while True:
|
|
490
594
|
out = _unchecked_fetchone()
|
|
491
595
|
if out is not None:
|
|
@@ -507,9 +611,10 @@ class SSCursor(Cursor):
|
|
|
507
611
|
for i in range(size):
|
|
508
612
|
row = self.read_next()
|
|
509
613
|
if row is None:
|
|
614
|
+
self.warning_count = self._result.warning_count
|
|
510
615
|
break
|
|
511
616
|
rows.append(row)
|
|
512
|
-
self.
|
|
617
|
+
self._rownumber += 1
|
|
513
618
|
return rows
|
|
514
619
|
|
|
515
620
|
def scroll(self, value, mode='relative'):
|
|
@@ -523,17 +628,17 @@ class SSCursor(Cursor):
|
|
|
523
628
|
|
|
524
629
|
for _ in range(value):
|
|
525
630
|
self.read_next()
|
|
526
|
-
self.
|
|
631
|
+
self._rownumber += value
|
|
527
632
|
elif mode == 'absolute':
|
|
528
|
-
if value < self.
|
|
633
|
+
if value < self._rownumber:
|
|
529
634
|
raise err.NotSupportedError(
|
|
530
635
|
'Backwards scrolling not supported by this cursor',
|
|
531
636
|
)
|
|
532
637
|
|
|
533
|
-
end = value - self.
|
|
638
|
+
end = value - self._rownumber
|
|
534
639
|
for _ in range(end):
|
|
535
640
|
self.read_next()
|
|
536
|
-
self.
|
|
641
|
+
self._rownumber = value
|
|
537
642
|
else:
|
|
538
643
|
raise err.ProgrammingError('unknown scroll mode %s' % mode)
|
|
539
644
|
|
|
@@ -546,7 +651,7 @@ class SSCursorSV(SSCursor):
|
|
|
546
651
|
row = self._result._read_rowdata_packet_unbuffered(1)
|
|
547
652
|
if row is None:
|
|
548
653
|
return None
|
|
549
|
-
self.
|
|
654
|
+
self._rownumber += 1
|
|
550
655
|
return row
|
|
551
656
|
|
|
552
657
|
def fetchone(self):
|
|
@@ -562,7 +667,10 @@ class SSCursorSV(SSCursor):
|
|
|
562
667
|
out = self._result._read_rowdata_packet_unbuffered(size)
|
|
563
668
|
if out is None:
|
|
564
669
|
return []
|
|
565
|
-
|
|
670
|
+
if size == 1:
|
|
671
|
+
self._rownumber += 1
|
|
672
|
+
return [out]
|
|
673
|
+
self._rownumber += len(out)
|
|
566
674
|
return out
|
|
567
675
|
|
|
568
676
|
def scroll(self, value, mode='relative'):
|
|
@@ -575,23 +683,31 @@ class SSCursorSV(SSCursor):
|
|
|
575
683
|
)
|
|
576
684
|
|
|
577
685
|
self._result._read_rowdata_packet_unbuffered(value)
|
|
578
|
-
self.
|
|
686
|
+
self._rownumber += value
|
|
579
687
|
elif mode == 'absolute':
|
|
580
|
-
if value < self.
|
|
688
|
+
if value < self._rownumber:
|
|
581
689
|
raise err.NotSupportedError(
|
|
582
690
|
'Backwards scrolling not supported by this cursor',
|
|
583
691
|
)
|
|
584
692
|
|
|
585
|
-
end = value - self.
|
|
693
|
+
end = value - self._rownumber
|
|
586
694
|
self._result._read_rowdata_packet_unbuffered(end)
|
|
587
|
-
self.
|
|
695
|
+
self._rownumber = value
|
|
588
696
|
else:
|
|
589
697
|
raise err.ProgrammingError('unknown scroll mode %s' % mode)
|
|
590
698
|
|
|
591
699
|
|
|
700
|
+
class SSDictCursor(DictCursorMixin, SSCursor):
|
|
701
|
+
"""An unbuffered cursor, which returns results as a dictionary"""
|
|
702
|
+
|
|
703
|
+
|
|
592
704
|
class SSDictCursorSV(SSCursorSV):
|
|
593
|
-
"""An unbuffered cursor for
|
|
705
|
+
"""An unbuffered cursor for the C extension, which returns a dictionary"""
|
|
594
706
|
|
|
595
707
|
|
|
596
|
-
class
|
|
597
|
-
"""An unbuffered cursor, which returns results as a
|
|
708
|
+
class SSNamedtupleCursor(NamedtupleCursorMixin, SSCursor):
|
|
709
|
+
"""An unbuffered cursor, which returns results as a named tuple"""
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
class SSNamedtupleCursorSV(SSCursorSV):
|
|
713
|
+
"""An unbuffered cursor for the C extension, which returns results as a named tuple"""
|