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.

Files changed (120) hide show
  1. singlestoredb/__init__.py +33 -1
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +5 -1
  4. singlestoredb/config.py +116 -14
  5. singlestoredb/connection.py +483 -516
  6. singlestoredb/converters.py +238 -135
  7. singlestoredb/exceptions.py +30 -2
  8. singlestoredb/functions/__init__.py +1 -0
  9. singlestoredb/functions/decorator.py +142 -0
  10. singlestoredb/functions/dtypes.py +1639 -0
  11. singlestoredb/functions/ext/__init__.py +2 -0
  12. singlestoredb/functions/ext/arrow.py +375 -0
  13. singlestoredb/functions/ext/asgi.py +661 -0
  14. singlestoredb/functions/ext/json.py +427 -0
  15. singlestoredb/functions/ext/mmap.py +306 -0
  16. singlestoredb/functions/ext/rowdat_1.py +744 -0
  17. singlestoredb/functions/signature.py +673 -0
  18. singlestoredb/fusion/__init__.py +11 -0
  19. singlestoredb/fusion/graphql.py +213 -0
  20. singlestoredb/fusion/handler.py +621 -0
  21. singlestoredb/fusion/handlers/stage.py +257 -0
  22. singlestoredb/fusion/handlers/utils.py +162 -0
  23. singlestoredb/fusion/handlers/workspace.py +412 -0
  24. singlestoredb/fusion/registry.py +164 -0
  25. singlestoredb/fusion/result.py +399 -0
  26. singlestoredb/http/__init__.py +27 -0
  27. singlestoredb/{http.py → http/connection.py} +555 -154
  28. singlestoredb/management/__init__.py +3 -0
  29. singlestoredb/management/billing_usage.py +148 -0
  30. singlestoredb/management/cluster.py +14 -6
  31. singlestoredb/management/manager.py +100 -38
  32. singlestoredb/management/organization.py +188 -0
  33. singlestoredb/management/region.py +5 -5
  34. singlestoredb/management/utils.py +281 -2
  35. singlestoredb/management/workspace.py +1344 -49
  36. singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
  37. singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
  38. singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
  39. singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
  40. singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
  41. singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
  42. singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
  43. singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
  44. singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
  45. singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
  46. singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
  47. singlestoredb/mysql/converters.py +271 -0
  48. singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
  49. singlestoredb/mysql/err.py +92 -0
  50. singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
  51. singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
  52. singlestoredb/mysql/tests/__init__.py +19 -0
  53. singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
  54. singlestoredb/mysql/tests/conftest.py +37 -0
  55. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
  56. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
  57. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
  58. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
  59. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
  60. singlestoredb/mysql/tests/test_cursor.py +141 -0
  61. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
  62. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
  63. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
  64. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
  65. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
  66. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
  67. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  68. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
  69. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
  70. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
  71. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
  72. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
  73. singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
  74. singlestoredb/pytest.py +283 -0
  75. singlestoredb/tests/empty.sql +0 -0
  76. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  77. singlestoredb/tests/test.sql +210 -0
  78. singlestoredb/tests/test2.sql +1 -0
  79. singlestoredb/tests/test_basics.py +482 -115
  80. singlestoredb/tests/test_config.py +13 -13
  81. singlestoredb/tests/test_connection.py +241 -305
  82. singlestoredb/tests/test_dbapi.py +27 -0
  83. singlestoredb/tests/test_ext_func.py +1193 -0
  84. singlestoredb/tests/test_ext_func_data.py +1101 -0
  85. singlestoredb/tests/test_fusion.py +465 -0
  86. singlestoredb/tests/test_http.py +32 -26
  87. singlestoredb/tests/test_management.py +588 -8
  88. singlestoredb/tests/test_plugin.py +33 -0
  89. singlestoredb/tests/test_results.py +11 -12
  90. singlestoredb/tests/test_udf.py +687 -0
  91. singlestoredb/tests/utils.py +3 -2
  92. singlestoredb/utils/config.py +58 -0
  93. singlestoredb/utils/debug.py +13 -0
  94. singlestoredb/utils/mogrify.py +151 -0
  95. singlestoredb/utils/results.py +4 -1
  96. singlestoredb-1.0.4.dist-info/METADATA +139 -0
  97. singlestoredb-1.0.4.dist-info/RECORD +112 -0
  98. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
  99. singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
  100. singlestoredb/clients/pymysqlsv/converters.py +0 -365
  101. singlestoredb/clients/pymysqlsv/err.py +0 -144
  102. singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
  103. singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
  104. singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
  105. singlestoredb/drivers/__init__.py +0 -45
  106. singlestoredb/drivers/base.py +0 -198
  107. singlestoredb/drivers/cymysql.py +0 -38
  108. singlestoredb/drivers/http.py +0 -47
  109. singlestoredb/drivers/mariadb.py +0 -40
  110. singlestoredb/drivers/mysqlconnector.py +0 -49
  111. singlestoredb/drivers/mysqldb.py +0 -60
  112. singlestoredb/drivers/pymysql.py +0 -37
  113. singlestoredb/drivers/pymysqlsv.py +0 -35
  114. singlestoredb/drivers/pyodbc.py +0 -65
  115. singlestoredb-0.4.0.dist-info/METADATA +0 -111
  116. singlestoredb-0.4.0.dist-info/RECORD +0 -86
  117. /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
  118. /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
  119. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
  120. {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
- def __init__(self, **kwargs):
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 = False
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
- """Representation of a MySQL response packet.
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
- """Read all remaining data in the packet.
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 'length' bytes."""
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
- """Get 'length' bytes starting at 'position'.
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
- """Read a 'Length Coded Binary' number from the data buffer.
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
- """Read a 'Length Coded String' from the data buffer.
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
- """A MysqlPacket that represents a specific column's metadata in the result.
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
- """Parse the 'Field Descriptor' (Metadata) packet.
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. It uses an existing packet object, and wraps
292
- around it, exposing useful variables while still providing access
293
- to the original packet objects variables and methods.
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. It uses an existing packet object, and wraps
320
- around it, exposing useful variables while still providing access
321
- to the original packet objects variables and methods.
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. It uses an existing packet object, and wraps
343
- around it, exposing useful variables while still providing access
344
- to the original packet objects variables and methods.
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.clients.pymysqlsv as sv
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': 'localhost',
23
- 'user': 'root',
24
- 'passwd': '',
25
- 'database': 'test1',
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
- """Return True if the given connection is on the version given or
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
- e.g.::
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
- self._connections.append(sv.connect(**params))
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
- """create a table.
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 singlestoredb.clients.pymysqlsv.cursors as cursors
6
- from singlestoredb.clients.pymysqlsv.tests import base
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(self.cursor_type)
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 for lack of table
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(self.cursor_type)
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.clients.pymysqlsv as sv
6
- from singlestoredb.clients.pymysqlsv.tests import base
7
- import singlestoredb.clients.pymysqlsv.cursors as cursors
8
- from singlestoredb.clients.pymysqlsv.constants import CLIENT
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('../pymysqlsv')
12
- import singlestoredb.clients.pymysqlsv as sv
13
- from singlestoredb.clients.pymysqlsv.tests import base
14
- import singlestoredb.clients.pymysqlsv.cursors as cursors
15
- from singlestoredb.clients.pymysqlsv.constants import CLIENT
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(client_flag=CLIENT.MULTI_STATEMENTS)
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(cursors.SSCursor)
41
+ cursor = conn.cursor()
37
42
 
38
43
  cursor.execute('DROP TABLE IF EXISTS tz_data')
39
44