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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from ..exceptions import DatabaseError # noqa: F401
|
|
5
|
+
from ..exceptions import DataError # noqa: F401
|
|
6
|
+
from ..exceptions import Error # noqa: F401
|
|
7
|
+
from ..exceptions import IntegrityError # noqa: F401
|
|
8
|
+
from ..exceptions import InterfaceError # noqa: F401
|
|
9
|
+
from ..exceptions import InternalError # noqa: F401
|
|
10
|
+
from ..exceptions import MySQLError # noqa: F401
|
|
11
|
+
from ..exceptions import NotSupportedError # noqa: F401
|
|
12
|
+
from ..exceptions import OperationalError # noqa: F401
|
|
13
|
+
from ..exceptions import ProgrammingError # noqa: F401
|
|
14
|
+
from ..exceptions import Warning # noqa: F401
|
|
15
|
+
from .constants import ER
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
error_map = {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _map_error(exc: Any, *errors: int) -> None:
|
|
22
|
+
for error in errors:
|
|
23
|
+
error_map[error] = exc
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_map_error(
|
|
27
|
+
ProgrammingError,
|
|
28
|
+
ER.DB_CREATE_EXISTS,
|
|
29
|
+
ER.SYNTAX_ERROR,
|
|
30
|
+
ER.PARSE_ERROR,
|
|
31
|
+
ER.NO_SUCH_TABLE,
|
|
32
|
+
ER.WRONG_DB_NAME,
|
|
33
|
+
ER.WRONG_TABLE_NAME,
|
|
34
|
+
ER.FIELD_SPECIFIED_TWICE,
|
|
35
|
+
ER.INVALID_GROUP_FUNC_USE,
|
|
36
|
+
ER.UNSUPPORTED_EXTENSION,
|
|
37
|
+
ER.TABLE_MUST_HAVE_COLUMNS,
|
|
38
|
+
ER.CANT_DO_THIS_DURING_AN_TRANSACTION,
|
|
39
|
+
ER.WRONG_DB_NAME,
|
|
40
|
+
ER.WRONG_COLUMN_NAME,
|
|
41
|
+
)
|
|
42
|
+
_map_error(
|
|
43
|
+
DataError,
|
|
44
|
+
ER.WARN_DATA_TRUNCATED,
|
|
45
|
+
ER.WARN_NULL_TO_NOTNULL,
|
|
46
|
+
ER.WARN_DATA_OUT_OF_RANGE,
|
|
47
|
+
ER.NO_DEFAULT,
|
|
48
|
+
ER.PRIMARY_CANT_HAVE_NULL,
|
|
49
|
+
ER.DATA_TOO_LONG,
|
|
50
|
+
ER.DATETIME_FUNCTION_OVERFLOW,
|
|
51
|
+
ER.TRUNCATED_WRONG_VALUE_FOR_FIELD,
|
|
52
|
+
ER.ILLEGAL_VALUE_FOR_TYPE,
|
|
53
|
+
)
|
|
54
|
+
_map_error(
|
|
55
|
+
IntegrityError,
|
|
56
|
+
ER.DUP_ENTRY,
|
|
57
|
+
ER.NO_REFERENCED_ROW,
|
|
58
|
+
ER.NO_REFERENCED_ROW_2,
|
|
59
|
+
ER.ROW_IS_REFERENCED,
|
|
60
|
+
ER.ROW_IS_REFERENCED_2,
|
|
61
|
+
ER.CANNOT_ADD_FOREIGN,
|
|
62
|
+
ER.BAD_NULL_ERROR,
|
|
63
|
+
)
|
|
64
|
+
_map_error(
|
|
65
|
+
NotSupportedError,
|
|
66
|
+
ER.WARNING_NOT_COMPLETE_ROLLBACK,
|
|
67
|
+
ER.NOT_SUPPORTED_YET,
|
|
68
|
+
ER.FEATURE_DISABLED,
|
|
69
|
+
ER.UNKNOWN_STORAGE_ENGINE,
|
|
70
|
+
)
|
|
71
|
+
_map_error(
|
|
72
|
+
OperationalError,
|
|
73
|
+
ER.DBACCESS_DENIED_ERROR,
|
|
74
|
+
ER.ACCESS_DENIED_ERROR,
|
|
75
|
+
ER.CON_COUNT_ERROR,
|
|
76
|
+
ER.TABLEACCESS_DENIED_ERROR,
|
|
77
|
+
ER.COLUMNACCESS_DENIED_ERROR,
|
|
78
|
+
ER.CONSTRAINT_FAILED,
|
|
79
|
+
ER.LOCK_DEADLOCK,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
del _map_error, ER
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def raise_mysql_exception(data: bytes) -> Exception:
|
|
87
|
+
errno = struct.unpack('<h', data[1:3])[0]
|
|
88
|
+
errval = data[9:].decode('utf-8', 'replace')
|
|
89
|
+
errorclass = error_map.get(errno)
|
|
90
|
+
if errorclass is None:
|
|
91
|
+
errorclass = InternalError if errno < 1000 else OperationalError
|
|
92
|
+
raise errorclass(errno, errval)
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
# type: ignore
|
|
2
1
|
import configparser
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Parser(configparser.RawConfigParser):
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
7
8
|
kwargs['allow_no_value'] = True
|
|
8
9
|
configparser.RawConfigParser.__init__(self, **kwargs)
|
|
9
10
|
|
|
10
|
-
def __remove_quotes(self, value):
|
|
11
|
+
def __remove_quotes(self, value: str) -> str:
|
|
11
12
|
quotes = ["'", '"']
|
|
12
13
|
for quote in quotes:
|
|
13
14
|
if len(value) >= 2 and value[0] == value[-1] == quote:
|
|
14
15
|
return value[1:-1]
|
|
15
16
|
return value
|
|
16
17
|
|
|
17
|
-
def get(self, section, option):
|
|
18
|
+
def get(self, section: str, option: str) -> str: # type: ignore
|
|
18
19
|
value = configparser.RawConfigParser.get(self, section, option)
|
|
19
20
|
return self.__remove_quotes(value)
|
|
@@ -5,12 +5,14 @@ import struct
|
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
7
|
from . import err
|
|
8
|
+
from ..config import get_option
|
|
9
|
+
from ..utils.results import Description
|
|
8
10
|
from .charset import MBLENGTH
|
|
9
11
|
from .constants import FIELD_TYPE
|
|
10
12
|
from .constants import SERVER_STATUS
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
DEBUG =
|
|
15
|
+
DEBUG = get_option('debug.connection')
|
|
14
16
|
|
|
15
17
|
NULL_COLUMN = 251
|
|
16
18
|
UNSIGNED_CHAR_COLUMN = 251
|
|
@@ -20,6 +22,7 @@ UNSIGNED_INT64_COLUMN = 254
|
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
def dump_packet(data): # pragma: no cover
|
|
25
|
+
|
|
23
26
|
def printable(data):
|
|
24
27
|
if 32 <= data < 127:
|
|
25
28
|
return chr(data)
|
|
@@ -46,9 +49,11 @@ def dump_packet(data): # pragma: no cover
|
|
|
46
49
|
|
|
47
50
|
|
|
48
51
|
class MysqlPacket:
|
|
49
|
-
"""
|
|
52
|
+
"""
|
|
53
|
+
Representation of a MySQL response packet.
|
|
50
54
|
|
|
51
55
|
Provides an interface for reading/parsing the packet results.
|
|
56
|
+
|
|
52
57
|
"""
|
|
53
58
|
|
|
54
59
|
__slots__ = ('_position', '_data')
|
|
@@ -77,16 +82,18 @@ class MysqlPacket:
|
|
|
77
82
|
return result
|
|
78
83
|
|
|
79
84
|
def read_all(self):
|
|
80
|
-
"""
|
|
85
|
+
"""
|
|
86
|
+
Read all remaining data in the packet.
|
|
81
87
|
|
|
82
88
|
(Subsequent read() will return errors.)
|
|
89
|
+
|
|
83
90
|
"""
|
|
84
91
|
result = self._data[self._position:]
|
|
85
92
|
self._position = None # ensure no subsequent read()
|
|
86
93
|
return result
|
|
87
94
|
|
|
88
95
|
def advance(self, length):
|
|
89
|
-
"""Advance the cursor in data buffer
|
|
96
|
+
"""Advance the cursor in data buffer ``length`` bytes."""
|
|
90
97
|
new_position = self._position + length
|
|
91
98
|
if new_position < 0 or new_position > len(self._data):
|
|
92
99
|
raise Exception(
|
|
@@ -102,13 +109,15 @@ class MysqlPacket:
|
|
|
102
109
|
self._position = position
|
|
103
110
|
|
|
104
111
|
def get_bytes(self, position, length=1):
|
|
105
|
-
"""
|
|
112
|
+
"""
|
|
113
|
+
Get 'length' bytes starting at 'position'.
|
|
106
114
|
|
|
107
115
|
Position is start of payload (first four packet header bytes are not
|
|
108
116
|
included) starting at index '0'.
|
|
109
117
|
|
|
110
118
|
No error checking is done. If requesting outside end of buffer
|
|
111
119
|
an empty string (or string shorter than 'length') may be returned!
|
|
120
|
+
|
|
112
121
|
"""
|
|
113
122
|
return self._data[position: (position + length)]
|
|
114
123
|
|
|
@@ -146,10 +155,12 @@ class MysqlPacket:
|
|
|
146
155
|
return result
|
|
147
156
|
|
|
148
157
|
def read_length_encoded_integer(self):
|
|
149
|
-
"""
|
|
158
|
+
"""
|
|
159
|
+
Read a 'Length Coded Binary' number from the data buffer.
|
|
150
160
|
|
|
151
161
|
Length coded numbers can be anywhere from 1 to 9 bytes depending
|
|
152
162
|
on the value of the first byte.
|
|
163
|
+
|
|
153
164
|
"""
|
|
154
165
|
c = self.read_uint8()
|
|
155
166
|
if c == NULL_COLUMN:
|
|
@@ -164,11 +175,13 @@ class MysqlPacket:
|
|
|
164
175
|
return self.read_uint64()
|
|
165
176
|
|
|
166
177
|
def read_length_coded_string(self):
|
|
167
|
-
"""
|
|
178
|
+
"""
|
|
179
|
+
Read a 'Length Coded String' from the data buffer.
|
|
168
180
|
|
|
169
181
|
A 'Length Coded String' consists first of a length coded
|
|
170
182
|
(unsigned, positive) integer represented in 1-9 bytes followed by
|
|
171
183
|
that many bytes of binary data. (For example "cat" would be "3cat".)
|
|
184
|
+
|
|
172
185
|
"""
|
|
173
186
|
length = self.read_length_encoded_integer()
|
|
174
187
|
if length is None:
|
|
@@ -226,10 +239,12 @@ class MysqlPacket:
|
|
|
226
239
|
|
|
227
240
|
|
|
228
241
|
class FieldDescriptorPacket(MysqlPacket):
|
|
229
|
-
"""
|
|
242
|
+
"""
|
|
243
|
+
A MysqlPacket that represents a specific column's metadata in the result.
|
|
230
244
|
|
|
231
245
|
Parsing is automatically done and the results are exported via public
|
|
232
246
|
attributes on the class such as: db, table_name, name, length, type_code.
|
|
247
|
+
|
|
233
248
|
"""
|
|
234
249
|
|
|
235
250
|
def __init__(self, data, encoding):
|
|
@@ -237,9 +252,11 @@ class FieldDescriptorPacket(MysqlPacket):
|
|
|
237
252
|
self._parse_field_descriptor(encoding)
|
|
238
253
|
|
|
239
254
|
def _parse_field_descriptor(self, encoding):
|
|
240
|
-
"""
|
|
255
|
+
"""
|
|
256
|
+
Parse the 'Field Descriptor' (Metadata) packet.
|
|
241
257
|
|
|
242
258
|
This is compatible with MySQL 4.1+ (not compatible with MySQL 4.0).
|
|
259
|
+
|
|
243
260
|
"""
|
|
244
261
|
self.catalog = self.read_length_coded_string()
|
|
245
262
|
self.db = self.read_length_coded_string()
|
|
@@ -259,7 +276,7 @@ class FieldDescriptorPacket(MysqlPacket):
|
|
|
259
276
|
|
|
260
277
|
def description(self):
|
|
261
278
|
"""Provides a 7-item tuple compatible with the Python PEP249 DB Spec."""
|
|
262
|
-
return (
|
|
279
|
+
return Description(
|
|
263
280
|
self.name,
|
|
264
281
|
self.type_code,
|
|
265
282
|
None, # TODO: display_length; should this be self.length?
|
|
@@ -267,6 +284,8 @@ class FieldDescriptorPacket(MysqlPacket):
|
|
|
267
284
|
self.get_column_length(), # 'precision' # TODO: why!?!?
|
|
268
285
|
self.scale,
|
|
269
286
|
self.flags % 2 == 0,
|
|
287
|
+
self.flags,
|
|
288
|
+
self.charsetnr,
|
|
270
289
|
)
|
|
271
290
|
|
|
272
291
|
def get_column_length(self):
|
|
@@ -276,21 +295,25 @@ class FieldDescriptorPacket(MysqlPacket):
|
|
|
276
295
|
return self.length
|
|
277
296
|
|
|
278
297
|
def __str__(self):
|
|
279
|
-
return '%s %r.%r.%r, type=%s, flags=%x' % (
|
|
298
|
+
return '%s %r.%r.%r, type=%s, flags=%x, charsetnr=%s' % (
|
|
280
299
|
self.__class__,
|
|
281
300
|
self.db,
|
|
282
301
|
self.table_name,
|
|
283
302
|
self.name,
|
|
284
303
|
self.type_code,
|
|
285
304
|
self.flags,
|
|
305
|
+
self.charsetnr,
|
|
286
306
|
)
|
|
287
307
|
|
|
288
308
|
|
|
289
309
|
class OKPacketWrapper:
|
|
290
310
|
"""
|
|
291
|
-
OK Packet Wrapper.
|
|
292
|
-
|
|
293
|
-
|
|
311
|
+
OK Packet Wrapper.
|
|
312
|
+
|
|
313
|
+
It uses an existing packet object, and wraps around it, exposing
|
|
314
|
+
useful variables while still providing access to the original packet
|
|
315
|
+
objects variables and methods.
|
|
316
|
+
|
|
294
317
|
"""
|
|
295
318
|
|
|
296
319
|
def __init__(self, from_packet):
|
|
@@ -316,9 +339,12 @@ class OKPacketWrapper:
|
|
|
316
339
|
|
|
317
340
|
class EOFPacketWrapper:
|
|
318
341
|
"""
|
|
319
|
-
EOF Packet Wrapper.
|
|
320
|
-
|
|
321
|
-
|
|
342
|
+
EOF Packet Wrapper.
|
|
343
|
+
|
|
344
|
+
It uses an existing packet object, and wraps around it, exposing
|
|
345
|
+
useful variables while still providing access to the original packet
|
|
346
|
+
objects variables and methods.
|
|
347
|
+
|
|
322
348
|
"""
|
|
323
349
|
|
|
324
350
|
def __init__(self, from_packet):
|
|
@@ -339,9 +365,12 @@ class EOFPacketWrapper:
|
|
|
339
365
|
|
|
340
366
|
class LoadLocalPacketWrapper:
|
|
341
367
|
"""
|
|
342
|
-
Load Local Packet Wrapper.
|
|
343
|
-
|
|
344
|
-
|
|
368
|
+
Load Local Packet Wrapper.
|
|
369
|
+
|
|
370
|
+
It uses an existing packet object, and wraps around it, exposing useful
|
|
371
|
+
variables while still providing access to the original packet
|
|
372
|
+
objects variables and methods.
|
|
373
|
+
|
|
345
374
|
"""
|
|
346
375
|
|
|
347
376
|
def __init__(self, from_packet):
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
# Sorted by alphabetical order
|
|
3
|
+
from singlestoredb.mysql.tests.test_basic import * # noqa: F403, F401
|
|
4
|
+
from singlestoredb.mysql.tests.test_connection import * # noqa: F403, F401
|
|
5
|
+
from singlestoredb.mysql.tests.test_converters import * # noqa: F403, F401
|
|
6
|
+
from singlestoredb.mysql.tests.test_cursor import * # noqa: F403, F401
|
|
7
|
+
from singlestoredb.mysql.tests.test_DictCursor import * # noqa: F403, F401
|
|
8
|
+
from singlestoredb.mysql.tests.test_err import * # noqa: F403, F401
|
|
9
|
+
from singlestoredb.mysql.tests.test_issues import * # noqa: F403, F401
|
|
10
|
+
from singlestoredb.mysql.tests.test_load_local import * # noqa: F403, F401
|
|
11
|
+
from singlestoredb.mysql.tests.test_nextset import * # noqa: F403, F401
|
|
12
|
+
from singlestoredb.mysql.tests.test_optionfile import * # noqa: F403, F401
|
|
13
|
+
from singlestoredb.mysql.tests.test_SSCursor import * # noqa: F403, F401
|
|
14
|
+
from singlestoredb.mysql.tests.thirdparty import * # noqa: F403, F401
|
|
15
|
+
|
|
16
|
+
if __name__ == '__main__':
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
unittest.main()
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
# type: ignore
|
|
2
|
-
import gc
|
|
3
2
|
import json
|
|
4
3
|
import os
|
|
4
|
+
import platform
|
|
5
5
|
import re
|
|
6
6
|
import unittest
|
|
7
7
|
import warnings
|
|
8
8
|
|
|
9
|
-
import singlestoredb.
|
|
9
|
+
import singlestoredb.mysql as sv
|
|
10
|
+
from singlestoredb.connection import build_params
|
|
11
|
+
|
|
12
|
+
DBNAME_BASE = 'singlestoredb__test_%s_%s_%s_%s_' % \
|
|
13
|
+
(
|
|
14
|
+
*platform.python_version_tuple()[:2],
|
|
15
|
+
platform.system(), platform.machine(),
|
|
16
|
+
)
|
|
10
17
|
|
|
11
18
|
|
|
12
19
|
class PyMySQLTestCase(unittest.TestCase):
|
|
@@ -17,31 +24,41 @@ class PyMySQLTestCase(unittest.TestCase):
|
|
|
17
24
|
with open(fname) as f:
|
|
18
25
|
databases = json.load(f)
|
|
19
26
|
else:
|
|
27
|
+
params = build_params()
|
|
20
28
|
databases = [
|
|
21
29
|
{
|
|
22
|
-
'host': '
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
30
|
+
'host': params['host'],
|
|
31
|
+
'port': params['port'],
|
|
32
|
+
'user': params['user'],
|
|
33
|
+
'passwd': params['password'],
|
|
34
|
+
'database': DBNAME_BASE + '1',
|
|
26
35
|
'use_unicode': True,
|
|
27
36
|
'local_infile': True,
|
|
37
|
+
'buffered': params['buffered'],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
'host': params['host'], 'user': params['user'],
|
|
41
|
+
'port': params['port'], 'passwd': params['password'],
|
|
42
|
+
'database': DBNAME_BASE + '2',
|
|
43
|
+
'buffered': params['buffered'],
|
|
28
44
|
},
|
|
29
|
-
{'host': 'localhost', 'user': 'root', 'passwd': '', 'database': 'test2'},
|
|
30
45
|
]
|
|
31
46
|
|
|
32
47
|
def mysql_server_is(self, conn, version_tuple):
|
|
33
|
-
"""
|
|
34
|
-
greater.
|
|
48
|
+
"""
|
|
49
|
+
Return True if the given connection is on the version given or greater.
|
|
35
50
|
|
|
36
51
|
This only checks the server version string provided when the
|
|
37
52
|
connection is established, therefore any check for a version tuple
|
|
38
53
|
greater than (5, 5, 5) will always fail on MariaDB, as it always
|
|
39
54
|
starts with 5.5.5, e.g. 5.5.5-10.7.1-MariaDB-1:10.7.1+maria~focal.
|
|
40
55
|
|
|
41
|
-
|
|
56
|
+
Examples
|
|
57
|
+
--------
|
|
42
58
|
|
|
43
59
|
if self.mysql_server_is(conn, (5, 6, 4)):
|
|
44
60
|
# do something for MySQL 5.6.4 and above
|
|
61
|
+
|
|
45
62
|
"""
|
|
46
63
|
server_version = conn.get_server_info()
|
|
47
64
|
server_version_tuple = tuple(
|
|
@@ -57,7 +74,8 @@ class PyMySQLTestCase(unittest.TestCase):
|
|
|
57
74
|
if self._connections is None:
|
|
58
75
|
self._connections = []
|
|
59
76
|
for params in self.databases:
|
|
60
|
-
|
|
77
|
+
conn = sv.connect(**params)
|
|
78
|
+
self._connections.append(conn)
|
|
61
79
|
self.addCleanup(self._teardown_connections)
|
|
62
80
|
return self._connections
|
|
63
81
|
|
|
@@ -81,12 +99,14 @@ class PyMySQLTestCase(unittest.TestCase):
|
|
|
81
99
|
self._connections = None
|
|
82
100
|
|
|
83
101
|
def safe_create_table(self, connection, tablename, ddl, cleanup=True):
|
|
84
|
-
"""
|
|
102
|
+
"""
|
|
103
|
+
Create a table.
|
|
85
104
|
|
|
86
105
|
Ensures any existing version of that table is first dropped.
|
|
87
106
|
|
|
88
107
|
Also adds a cleanup rule to drop the table after the test
|
|
89
108
|
completes.
|
|
109
|
+
|
|
90
110
|
"""
|
|
91
111
|
cursor = connection.cursor()
|
|
92
112
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
|
|
3
|
+
import singlestoredb.mysql as sv
|
|
4
|
+
from singlestoredb.connection import build_params
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DBNAME_BASE = 'singlestoredb__test_%s_%s_%s_%s_' % \
|
|
8
|
+
(
|
|
9
|
+
*platform.python_version_tuple()[:2],
|
|
10
|
+
platform.system(), platform.machine(),
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def pytest_sessionstart() -> None:
|
|
15
|
+
params = build_params()
|
|
16
|
+
conn = sv.connect( # type: ignore
|
|
17
|
+
host=params['host'], user=params['user'],
|
|
18
|
+
passwd=params['password'], port=params['port'],
|
|
19
|
+
buffered=params['buffered'],
|
|
20
|
+
)
|
|
21
|
+
cur = conn.cursor()
|
|
22
|
+
cur.execute(f'CREATE DATABASE IF NOT EXISTS {DBNAME_BASE}1')
|
|
23
|
+
cur.execute(f'CREATE DATABASE IF NOT EXISTS {DBNAME_BASE}2')
|
|
24
|
+
conn.close()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def pytest_sessionfinish() -> None:
|
|
28
|
+
params = build_params()
|
|
29
|
+
conn = sv.connect( # type: ignore
|
|
30
|
+
host=params['host'], user=params['user'],
|
|
31
|
+
passwd=params['password'], port=params['port'],
|
|
32
|
+
buffered=params['buffered'],
|
|
33
|
+
)
|
|
34
|
+
cur = conn.cursor()
|
|
35
|
+
cur.execute(f'DROP DATABASE {DBNAME_BASE}1')
|
|
36
|
+
cur.execute(f'DROP DATABASE {DBNAME_BASE}2')
|
|
37
|
+
conn.close()
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
import datetime
|
|
3
3
|
import warnings
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import singlestoredb.mysql.cursors as cursors
|
|
8
|
+
from singlestoredb.mysql.tests import base
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class TestDictCursor(base.PyMySQLTestCase):
|
|
12
|
+
|
|
10
13
|
bob = {'name': 'bob', 'age': 21, 'DOB': datetime.datetime(1990, 2, 6, 23, 4, 56)}
|
|
11
14
|
jim = {'name': 'jim', 'age': 56, 'DOB': datetime.datetime(1955, 5, 9, 13, 12, 45)}
|
|
12
15
|
fred = {'name': 'fred', 'age': 100, 'DOB': datetime.datetime(1911, 9, 12, 1, 1, 1)}
|
|
@@ -15,15 +18,15 @@ class TestDictCursor(base.PyMySQLTestCase):
|
|
|
15
18
|
|
|
16
19
|
def setUp(self):
|
|
17
20
|
super(TestDictCursor, self).setUp()
|
|
18
|
-
self.conn = conn = self.connect()
|
|
19
|
-
c = conn.cursor(
|
|
21
|
+
self.conn = conn = self.connect(cursorclass=self.cursor_type)
|
|
22
|
+
c = conn.cursor()
|
|
20
23
|
|
|
21
24
|
# create a table ane some data to query
|
|
22
25
|
with warnings.catch_warnings():
|
|
23
26
|
warnings.filterwarnings('ignore')
|
|
24
27
|
c.execute('drop table if exists dictcursor')
|
|
25
|
-
# include in filterwarnings since for unbuffered dict cursor warning
|
|
26
|
-
# will only be propagated at start of next execute() call
|
|
28
|
+
# include in filterwarnings since for unbuffered dict cursor warning
|
|
29
|
+
# for lack of table will only be propagated at start of next execute() call
|
|
27
30
|
c.execute(
|
|
28
31
|
"""CREATE TABLE dictcursor (name char(20), age int , DOB datetime)""",
|
|
29
32
|
)
|
|
@@ -46,7 +49,7 @@ class TestDictCursor(base.PyMySQLTestCase):
|
|
|
46
49
|
bob, jim, fred = self.bob.copy(), self.jim.copy(), self.fred.copy()
|
|
47
50
|
# all assert test compare to the structure as would come out from MySQLdb
|
|
48
51
|
conn = self.conn
|
|
49
|
-
c = conn.cursor(
|
|
52
|
+
c = conn.cursor()
|
|
50
53
|
|
|
51
54
|
# try an update which should return no rows
|
|
52
55
|
c.execute("update dictcursor set age=20 where name='bob'")
|
|
@@ -83,6 +86,7 @@ class TestDictCursor(base.PyMySQLTestCase):
|
|
|
83
86
|
self.assertEqual([bob, fred], r, 'fetchmany failed via DictCursor')
|
|
84
87
|
self._ensure_cursor_expired(c)
|
|
85
88
|
|
|
89
|
+
@pytest.mark.skip('Custom cursors are only available when creating a connection')
|
|
86
90
|
def test_custom_dict(self):
|
|
87
91
|
class MyDict(dict):
|
|
88
92
|
pass
|
|
@@ -2,24 +2,29 @@
|
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
4
|
try:
|
|
5
|
-
import singlestoredb.
|
|
6
|
-
from singlestoredb.
|
|
7
|
-
import singlestoredb.
|
|
8
|
-
from singlestoredb.
|
|
5
|
+
import singlestoredb.mysql as sv
|
|
6
|
+
from singlestoredb.mysql.tests import base
|
|
7
|
+
import singlestoredb.mysql.cursors as cursors
|
|
8
|
+
from singlestoredb.mysql.constants import CLIENT
|
|
9
9
|
except Exception:
|
|
10
10
|
# For local testing from top-level directory, without installing
|
|
11
|
-
sys.path.append('
|
|
12
|
-
import singlestoredb.
|
|
13
|
-
from singlestoredb.
|
|
14
|
-
import singlestoredb.
|
|
15
|
-
from singlestoredb.
|
|
11
|
+
sys.path.append('../../..')
|
|
12
|
+
import singlestoredb.mysql as sv # noqa: F401
|
|
13
|
+
from singlestoredb.mysql.tests import base
|
|
14
|
+
import singlestoredb.mysql.cursors as cursors
|
|
15
|
+
from singlestoredb.mysql.constants import CLIENT
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class TestSSCursor(base.PyMySQLTestCase):
|
|
19
|
+
|
|
19
20
|
def test_SSCursor(self):
|
|
20
|
-
affected_rows = 18446744073709551615
|
|
21
|
+
# affected_rows = 18446744073709551615
|
|
22
|
+
affected_rows = -1
|
|
21
23
|
|
|
22
|
-
conn = self.connect(
|
|
24
|
+
conn = self.connect(
|
|
25
|
+
client_flag=CLIENT.MULTI_STATEMENTS,
|
|
26
|
+
cursorclass=cursors.SSCursor,
|
|
27
|
+
)
|
|
23
28
|
data = [
|
|
24
29
|
('America', '', 'America/Jamaica'),
|
|
25
30
|
('America', '', 'America/Los_Angeles'),
|
|
@@ -33,7 +38,7 @@ class TestSSCursor(base.PyMySQLTestCase):
|
|
|
33
38
|
('America', '', 'America/Detroit'),
|
|
34
39
|
]
|
|
35
40
|
|
|
36
|
-
cursor = conn.cursor(
|
|
41
|
+
cursor = conn.cursor()
|
|
37
42
|
|
|
38
43
|
cursor.execute('DROP TABLE IF EXISTS tz_data')
|
|
39
44
|
|