singlestoredb 1.16.1__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.
Files changed (183) hide show
  1. singlestoredb/__init__.py +75 -0
  2. singlestoredb/ai/__init__.py +2 -0
  3. singlestoredb/ai/chat.py +139 -0
  4. singlestoredb/ai/embeddings.py +128 -0
  5. singlestoredb/alchemy/__init__.py +90 -0
  6. singlestoredb/apps/__init__.py +3 -0
  7. singlestoredb/apps/_cloud_functions.py +90 -0
  8. singlestoredb/apps/_config.py +72 -0
  9. singlestoredb/apps/_connection_info.py +18 -0
  10. singlestoredb/apps/_dashboards.py +47 -0
  11. singlestoredb/apps/_process.py +32 -0
  12. singlestoredb/apps/_python_udfs.py +100 -0
  13. singlestoredb/apps/_stdout_supress.py +30 -0
  14. singlestoredb/apps/_uvicorn_util.py +36 -0
  15. singlestoredb/auth.py +245 -0
  16. singlestoredb/config.py +484 -0
  17. singlestoredb/connection.py +1487 -0
  18. singlestoredb/converters.py +950 -0
  19. singlestoredb/docstring/__init__.py +33 -0
  20. singlestoredb/docstring/attrdoc.py +126 -0
  21. singlestoredb/docstring/common.py +230 -0
  22. singlestoredb/docstring/epydoc.py +267 -0
  23. singlestoredb/docstring/google.py +412 -0
  24. singlestoredb/docstring/numpydoc.py +562 -0
  25. singlestoredb/docstring/parser.py +100 -0
  26. singlestoredb/docstring/py.typed +1 -0
  27. singlestoredb/docstring/rest.py +256 -0
  28. singlestoredb/docstring/tests/__init__.py +1 -0
  29. singlestoredb/docstring/tests/_pydoctor.py +21 -0
  30. singlestoredb/docstring/tests/test_epydoc.py +729 -0
  31. singlestoredb/docstring/tests/test_google.py +1007 -0
  32. singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
  33. singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
  34. singlestoredb/docstring/tests/test_parser.py +248 -0
  35. singlestoredb/docstring/tests/test_rest.py +547 -0
  36. singlestoredb/docstring/tests/test_util.py +70 -0
  37. singlestoredb/docstring/util.py +141 -0
  38. singlestoredb/exceptions.py +120 -0
  39. singlestoredb/functions/__init__.py +16 -0
  40. singlestoredb/functions/decorator.py +201 -0
  41. singlestoredb/functions/dtypes.py +1793 -0
  42. singlestoredb/functions/ext/__init__.py +1 -0
  43. singlestoredb/functions/ext/arrow.py +375 -0
  44. singlestoredb/functions/ext/asgi.py +2133 -0
  45. singlestoredb/functions/ext/json.py +420 -0
  46. singlestoredb/functions/ext/mmap.py +413 -0
  47. singlestoredb/functions/ext/rowdat_1.py +724 -0
  48. singlestoredb/functions/ext/timer.py +89 -0
  49. singlestoredb/functions/ext/utils.py +218 -0
  50. singlestoredb/functions/signature.py +1578 -0
  51. singlestoredb/functions/typing/__init__.py +41 -0
  52. singlestoredb/functions/typing/numpy.py +20 -0
  53. singlestoredb/functions/typing/pandas.py +2 -0
  54. singlestoredb/functions/typing/polars.py +2 -0
  55. singlestoredb/functions/typing/pyarrow.py +2 -0
  56. singlestoredb/functions/utils.py +421 -0
  57. singlestoredb/fusion/__init__.py +11 -0
  58. singlestoredb/fusion/graphql.py +213 -0
  59. singlestoredb/fusion/handler.py +916 -0
  60. singlestoredb/fusion/handlers/__init__.py +0 -0
  61. singlestoredb/fusion/handlers/export.py +525 -0
  62. singlestoredb/fusion/handlers/files.py +690 -0
  63. singlestoredb/fusion/handlers/job.py +660 -0
  64. singlestoredb/fusion/handlers/models.py +250 -0
  65. singlestoredb/fusion/handlers/stage.py +502 -0
  66. singlestoredb/fusion/handlers/utils.py +324 -0
  67. singlestoredb/fusion/handlers/workspace.py +956 -0
  68. singlestoredb/fusion/registry.py +249 -0
  69. singlestoredb/fusion/result.py +399 -0
  70. singlestoredb/http/__init__.py +27 -0
  71. singlestoredb/http/connection.py +1267 -0
  72. singlestoredb/magics/__init__.py +34 -0
  73. singlestoredb/magics/run_personal.py +137 -0
  74. singlestoredb/magics/run_shared.py +134 -0
  75. singlestoredb/management/__init__.py +9 -0
  76. singlestoredb/management/billing_usage.py +148 -0
  77. singlestoredb/management/cluster.py +462 -0
  78. singlestoredb/management/export.py +295 -0
  79. singlestoredb/management/files.py +1102 -0
  80. singlestoredb/management/inference_api.py +105 -0
  81. singlestoredb/management/job.py +887 -0
  82. singlestoredb/management/manager.py +373 -0
  83. singlestoredb/management/organization.py +226 -0
  84. singlestoredb/management/region.py +169 -0
  85. singlestoredb/management/utils.py +423 -0
  86. singlestoredb/management/workspace.py +1927 -0
  87. singlestoredb/mysql/__init__.py +177 -0
  88. singlestoredb/mysql/_auth.py +298 -0
  89. singlestoredb/mysql/charset.py +214 -0
  90. singlestoredb/mysql/connection.py +2032 -0
  91. singlestoredb/mysql/constants/CLIENT.py +38 -0
  92. singlestoredb/mysql/constants/COMMAND.py +32 -0
  93. singlestoredb/mysql/constants/CR.py +78 -0
  94. singlestoredb/mysql/constants/ER.py +474 -0
  95. singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
  96. singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
  97. singlestoredb/mysql/constants/FLAG.py +15 -0
  98. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  99. singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
  100. singlestoredb/mysql/constants/__init__.py +0 -0
  101. singlestoredb/mysql/converters.py +271 -0
  102. singlestoredb/mysql/cursors.py +896 -0
  103. singlestoredb/mysql/err.py +92 -0
  104. singlestoredb/mysql/optionfile.py +20 -0
  105. singlestoredb/mysql/protocol.py +450 -0
  106. singlestoredb/mysql/tests/__init__.py +19 -0
  107. singlestoredb/mysql/tests/base.py +126 -0
  108. singlestoredb/mysql/tests/conftest.py +37 -0
  109. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  110. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  111. singlestoredb/mysql/tests/test_basic.py +452 -0
  112. singlestoredb/mysql/tests/test_connection.py +851 -0
  113. singlestoredb/mysql/tests/test_converters.py +58 -0
  114. singlestoredb/mysql/tests/test_cursor.py +141 -0
  115. singlestoredb/mysql/tests/test_err.py +16 -0
  116. singlestoredb/mysql/tests/test_issues.py +514 -0
  117. singlestoredb/mysql/tests/test_load_local.py +75 -0
  118. singlestoredb/mysql/tests/test_nextset.py +88 -0
  119. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  120. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  121. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  122. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  123. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  124. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  125. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  126. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  127. singlestoredb/mysql/times.py +23 -0
  128. singlestoredb/notebook/__init__.py +16 -0
  129. singlestoredb/notebook/_objects.py +213 -0
  130. singlestoredb/notebook/_portal.py +352 -0
  131. singlestoredb/py.typed +0 -0
  132. singlestoredb/pytest.py +352 -0
  133. singlestoredb/server/__init__.py +0 -0
  134. singlestoredb/server/docker.py +452 -0
  135. singlestoredb/server/free_tier.py +267 -0
  136. singlestoredb/tests/__init__.py +0 -0
  137. singlestoredb/tests/alltypes.sql +307 -0
  138. singlestoredb/tests/alltypes_no_nulls.sql +208 -0
  139. singlestoredb/tests/empty.sql +0 -0
  140. singlestoredb/tests/ext_funcs/__init__.py +702 -0
  141. singlestoredb/tests/local_infile.csv +3 -0
  142. singlestoredb/tests/test.ipynb +18 -0
  143. singlestoredb/tests/test.sql +680 -0
  144. singlestoredb/tests/test2.ipynb +18 -0
  145. singlestoredb/tests/test2.sql +1 -0
  146. singlestoredb/tests/test_basics.py +1332 -0
  147. singlestoredb/tests/test_config.py +318 -0
  148. singlestoredb/tests/test_connection.py +3103 -0
  149. singlestoredb/tests/test_dbapi.py +27 -0
  150. singlestoredb/tests/test_exceptions.py +45 -0
  151. singlestoredb/tests/test_ext_func.py +1472 -0
  152. singlestoredb/tests/test_ext_func_data.py +1101 -0
  153. singlestoredb/tests/test_fusion.py +1527 -0
  154. singlestoredb/tests/test_http.py +288 -0
  155. singlestoredb/tests/test_management.py +1599 -0
  156. singlestoredb/tests/test_plugin.py +33 -0
  157. singlestoredb/tests/test_results.py +171 -0
  158. singlestoredb/tests/test_types.py +132 -0
  159. singlestoredb/tests/test_udf.py +737 -0
  160. singlestoredb/tests/test_udf_returns.py +459 -0
  161. singlestoredb/tests/test_vectorstore.py +51 -0
  162. singlestoredb/tests/test_xdict.py +333 -0
  163. singlestoredb/tests/utils.py +141 -0
  164. singlestoredb/types.py +373 -0
  165. singlestoredb/utils/__init__.py +0 -0
  166. singlestoredb/utils/config.py +950 -0
  167. singlestoredb/utils/convert_rows.py +69 -0
  168. singlestoredb/utils/debug.py +13 -0
  169. singlestoredb/utils/dtypes.py +205 -0
  170. singlestoredb/utils/events.py +65 -0
  171. singlestoredb/utils/mogrify.py +151 -0
  172. singlestoredb/utils/results.py +585 -0
  173. singlestoredb/utils/xdict.py +425 -0
  174. singlestoredb/vectorstore.py +192 -0
  175. singlestoredb/warnings.py +5 -0
  176. singlestoredb-1.16.1.dist-info/METADATA +165 -0
  177. singlestoredb-1.16.1.dist-info/RECORD +183 -0
  178. singlestoredb-1.16.1.dist-info/WHEEL +5 -0
  179. singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
  180. singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
  181. singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
  182. sqlx/__init__.py +4 -0
  183. sqlx/magic.py +113 -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)
@@ -0,0 +1,20 @@
1
+ import configparser
2
+ from typing import Any
3
+
4
+
5
+ class Parser(configparser.RawConfigParser):
6
+
7
+ def __init__(self, **kwargs: Any) -> None:
8
+ kwargs['allow_no_value'] = True
9
+ configparser.RawConfigParser.__init__(self, **kwargs)
10
+
11
+ def __remove_quotes(self, value: str) -> str:
12
+ quotes = ["'", '"']
13
+ for quote in quotes:
14
+ if len(value) >= 2 and value[0] == value[-1] == quote:
15
+ return value[1:-1]
16
+ return value
17
+
18
+ def get(self, section: str, option: str) -> str: # type: ignore
19
+ value = configparser.RawConfigParser.get(self, section, option)
20
+ return self.__remove_quotes(value)
@@ -0,0 +1,450 @@
1
+ # type: ignore
2
+ # Python implementation of low level MySQL client-server protocol
3
+ # http://dev.mysql.com/doc/internals/en/client-server-protocol.html
4
+ import struct
5
+ import sys
6
+
7
+ from . import err
8
+ from ..config import get_option
9
+ from ..utils.results import Description
10
+ from .charset import MBLENGTH
11
+ from .constants import EXTENDED_TYPE
12
+ from .constants import FIELD_TYPE
13
+ from .constants import SERVER_STATUS
14
+ from .constants import VECTOR_TYPE
15
+
16
+
17
+ DEBUG = get_option('debug.connection')
18
+
19
+ NULL_COLUMN = 251
20
+ UNSIGNED_CHAR_COLUMN = 251
21
+ UNSIGNED_SHORT_COLUMN = 252
22
+ UNSIGNED_INT24_COLUMN = 253
23
+ UNSIGNED_INT64_COLUMN = 254
24
+
25
+
26
+ def dump_packet(data): # pragma: no cover
27
+
28
+ def printable(data):
29
+ if 32 <= data < 127:
30
+ return chr(data)
31
+ return '.'
32
+
33
+ try:
34
+ print('packet length:', len(data))
35
+ for i in range(1, 7):
36
+ f = sys._getframe(i)
37
+ print('call[%d]: %s (line %d)' % (i, f.f_code.co_name, f.f_lineno))
38
+ print('-' * 66)
39
+ except ValueError:
40
+ pass
41
+ dump_data = [data[i: i + 16] for i in range(0, min(len(data), 256), 16)]
42
+ for d in dump_data:
43
+ print(
44
+ ' '.join('{:02X}'.format(x) for x in d)
45
+ + ' ' * (16 - len(d))
46
+ + ' ' * 2
47
+ + ''.join(printable(x) for x in d),
48
+ )
49
+ print('-' * 66)
50
+ print()
51
+
52
+
53
+ class MysqlPacket:
54
+ """
55
+ Representation of a MySQL response packet.
56
+
57
+ Provides an interface for reading/parsing the packet results.
58
+
59
+ """
60
+
61
+ __slots__ = ('_position', '_data')
62
+
63
+ def __init__(self, data, encoding):
64
+ self._position = 0
65
+ self._data = data
66
+
67
+ def get_all_data(self):
68
+ return self._data
69
+
70
+ def read(self, size):
71
+ """Read the first 'size' bytes in packet and advance cursor past them."""
72
+ result = self._data[self._position: (self._position + size)]
73
+ if len(result) != size:
74
+ error = (
75
+ 'Result length not requested length:\n'
76
+ 'Expected=%s. Actual=%s. Position: %s. Data Length: %s'
77
+ % (size, len(result), self._position, len(self._data))
78
+ )
79
+ if DEBUG:
80
+ print(error)
81
+ self.dump()
82
+ raise AssertionError(error)
83
+ self._position += size
84
+ return result
85
+
86
+ def read_all(self):
87
+ """
88
+ Read all remaining data in the packet.
89
+
90
+ (Subsequent read() will return errors.)
91
+
92
+ """
93
+ result = self._data[self._position:]
94
+ self._position = None # ensure no subsequent read()
95
+ return result
96
+
97
+ def advance(self, length):
98
+ """Advance the cursor in data buffer ``length`` bytes."""
99
+ new_position = self._position + length
100
+ if new_position < 0 or new_position > len(self._data):
101
+ raise Exception(
102
+ 'Invalid advance amount (%s) for cursor. '
103
+ 'Position=%s' % (length, new_position),
104
+ )
105
+ self._position = new_position
106
+
107
+ def rewind(self, position=0):
108
+ """Set the position of the data buffer cursor to 'position'."""
109
+ if position < 0 or position > len(self._data):
110
+ raise Exception('Invalid position to rewind cursor to: %s.' % position)
111
+ self._position = position
112
+
113
+ def get_bytes(self, position, length=1):
114
+ """
115
+ Get 'length' bytes starting at 'position'.
116
+
117
+ Position is start of payload (first four packet header bytes are not
118
+ included) starting at index '0'.
119
+
120
+ No error checking is done. If requesting outside end of buffer
121
+ an empty string (or string shorter than 'length') may be returned!
122
+
123
+ """
124
+ return self._data[position: (position + length)]
125
+
126
+ def read_uint8(self):
127
+ result = self._data[self._position]
128
+ self._position += 1
129
+ return result
130
+
131
+ def read_uint16(self):
132
+ result = struct.unpack_from('<H', self._data, self._position)[0]
133
+ self._position += 2
134
+ return result
135
+
136
+ def read_uint24(self):
137
+ low, high = struct.unpack_from('<HB', self._data, self._position)
138
+ self._position += 3
139
+ return low + (high << 16)
140
+
141
+ def read_uint32(self):
142
+ result = struct.unpack_from('<I', self._data, self._position)[0]
143
+ self._position += 4
144
+ return result
145
+
146
+ def read_uint64(self):
147
+ result = struct.unpack_from('<Q', self._data, self._position)[0]
148
+ self._position += 8
149
+ return result
150
+
151
+ def read_string(self):
152
+ end_pos = self._data.find(b'\0', self._position)
153
+ if end_pos < 0:
154
+ return None
155
+ result = self._data[self._position: end_pos]
156
+ self._position = end_pos + 1
157
+ return result
158
+
159
+ def read_length_encoded_integer(self):
160
+ """
161
+ Read a 'Length Coded Binary' number from the data buffer.
162
+
163
+ Length coded numbers can be anywhere from 1 to 9 bytes depending
164
+ on the value of the first byte.
165
+
166
+ """
167
+ c = self.read_uint8()
168
+ if c == NULL_COLUMN:
169
+ return None
170
+ if c < UNSIGNED_CHAR_COLUMN:
171
+ return c
172
+ elif c == UNSIGNED_SHORT_COLUMN:
173
+ return self.read_uint16()
174
+ elif c == UNSIGNED_INT24_COLUMN:
175
+ return self.read_uint24()
176
+ elif c == UNSIGNED_INT64_COLUMN:
177
+ return self.read_uint64()
178
+
179
+ def read_length_coded_string(self):
180
+ """
181
+ Read a 'Length Coded String' from the data buffer.
182
+
183
+ A 'Length Coded String' consists first of a length coded
184
+ (unsigned, positive) integer represented in 1-9 bytes followed by
185
+ that many bytes of binary data. (For example "cat" would be "3cat".)
186
+
187
+ """
188
+ length = self.read_length_encoded_integer()
189
+ if length is None:
190
+ return None
191
+ return self.read(length)
192
+
193
+ def read_struct(self, fmt):
194
+ s = struct.Struct(fmt)
195
+ result = s.unpack_from(self._data, self._position)
196
+ self._position += s.size
197
+ return result
198
+
199
+ def is_ok_packet(self):
200
+ # https://dev.mysql.com/doc/internals/en/packet-OK_Packet.html
201
+ return self._data[0] == 0 and len(self._data) >= 7
202
+
203
+ def is_eof_packet(self):
204
+ # http://dev.mysql.com/doc/internals/en/generic-response-packets.html#packet-EOF_Packet
205
+ # Caution: \xFE may be LengthEncodedInteger.
206
+ # If \xFE is LengthEncodedInteger header, 8bytes followed.
207
+ return self._data[0] == 0xFE and len(self._data) < 9
208
+
209
+ def is_auth_switch_request(self):
210
+ # http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest
211
+ return self._data[0] == 0xFE
212
+
213
+ def is_extra_auth_data(self):
214
+ # https://dev.mysql.com/doc/internals/en/successful-authentication.html
215
+ return self._data[0] == 1
216
+
217
+ def is_resultset_packet(self):
218
+ field_count = self._data[0]
219
+ return 1 <= field_count <= 250
220
+
221
+ def is_load_local_packet(self):
222
+ return self._data[0] == 0xFB
223
+
224
+ def is_error_packet(self):
225
+ return self._data[0] == 0xFF
226
+
227
+ def check_error(self):
228
+ if self.is_error_packet():
229
+ self.raise_for_error()
230
+
231
+ def raise_for_error(self):
232
+ self.rewind()
233
+ self.advance(1) # field_count == error (we already know that)
234
+ errno = self.read_uint16()
235
+ if DEBUG:
236
+ print('errno =', errno)
237
+ err.raise_mysql_exception(self._data)
238
+
239
+ def dump(self):
240
+ dump_packet(self._data)
241
+
242
+
243
+ class FieldDescriptorPacket(MysqlPacket):
244
+ """
245
+ A MysqlPacket that represents a specific column's metadata in the result.
246
+
247
+ Parsing is automatically done and the results are exported via public
248
+ attributes on the class such as: db, table_name, name, length, type_code.
249
+
250
+ """
251
+
252
+ def __init__(self, data, encoding):
253
+ MysqlPacket.__init__(self, data, encoding)
254
+ self._parse_field_descriptor(encoding)
255
+
256
+ def _parse_field_descriptor(self, encoding):
257
+ """
258
+ Parse the 'Field Descriptor' (Metadata) packet.
259
+
260
+ This is compatible with MySQL 4.1+ (not compatible with MySQL 4.0).
261
+
262
+ """
263
+ self.catalog = self.read_length_coded_string()
264
+ self.db = self.read_length_coded_string()
265
+ self.table_name = self.read_length_coded_string().decode(encoding)
266
+ self.org_table = self.read_length_coded_string().decode(encoding)
267
+ self.name = self.read_length_coded_string().decode(encoding)
268
+ self.org_name = self.read_length_coded_string().decode(encoding)
269
+ n_bytes = 0
270
+ (
271
+ n_bytes,
272
+ self.charsetnr,
273
+ self.length,
274
+ self.type_code,
275
+ self.flags,
276
+ self.scale,
277
+ ) = self.read_struct('<BHIBHBxx')
278
+
279
+ # 'default' is a length coded binary and is still in the buffer?
280
+ # not used for normal result sets...
281
+
282
+ # Extended types
283
+ if n_bytes > 12:
284
+ ext_type_code = self.read_uint8()
285
+ if ext_type_code == EXTENDED_TYPE.NONE:
286
+ pass
287
+ elif ext_type_code == EXTENDED_TYPE.BSON:
288
+ self.type_code = FIELD_TYPE.BSON
289
+ elif ext_type_code == EXTENDED_TYPE.VECTOR:
290
+ (self.length, vec_type) = self.read_struct('<IB')
291
+ if vec_type == VECTOR_TYPE.FLOAT32:
292
+ if self.charsetnr == 63:
293
+ self.type_code = FIELD_TYPE.FLOAT32_VECTOR
294
+ else:
295
+ self.type_code = FIELD_TYPE.FLOAT32_VECTOR_JSON
296
+ elif vec_type == VECTOR_TYPE.FLOAT64:
297
+ if self.charsetnr == 63:
298
+ self.type_code = FIELD_TYPE.FLOAT64_VECTOR
299
+ else:
300
+ self.type_code = FIELD_TYPE.FLOAT64_VECTOR_JSON
301
+ elif vec_type == VECTOR_TYPE.INT8:
302
+ if self.charsetnr == 63:
303
+ self.type_code = FIELD_TYPE.INT8_VECTOR
304
+ else:
305
+ self.type_code = FIELD_TYPE.INT8_VECTOR_JSON
306
+ elif vec_type == VECTOR_TYPE.INT16:
307
+ if self.charsetnr == 63:
308
+ self.type_code = FIELD_TYPE.INT16_VECTOR
309
+ else:
310
+ self.type_code = FIELD_TYPE.INT16_VECTOR_JSON
311
+ elif vec_type == VECTOR_TYPE.INT32:
312
+ if self.charsetnr == 63:
313
+ self.type_code = FIELD_TYPE.INT32_VECTOR
314
+ else:
315
+ self.type_code = FIELD_TYPE.INT32_VECTOR_JSON
316
+ elif vec_type == VECTOR_TYPE.INT64:
317
+ if self.charsetnr == 63:
318
+ self.type_code = FIELD_TYPE.INT64_VECTOR
319
+ else:
320
+ self.type_code = FIELD_TYPE.INT64_VECTOR_JSON
321
+ else:
322
+ raise TypeError(f'unrecognized vector data type: {vec_type}')
323
+ else:
324
+ raise TypeError(f'unrecognized extended data type: {ext_type_code}')
325
+
326
+ def description(self):
327
+ """
328
+ Provides a 9-item tuple.
329
+
330
+ Standard descriptions only have 7 fields according to the Python
331
+ PEP249 DB Spec, but we need to surface information about unsigned
332
+ types and charsetnr for proper type handling.
333
+
334
+ """
335
+ precision = self.get_column_length()
336
+ if self.type_code in (FIELD_TYPE.DECIMAL, FIELD_TYPE.NEWDECIMAL):
337
+ if precision:
338
+ precision -= 1 # for the sign
339
+ if self.scale > 0:
340
+ precision -= 1 # for the decimal point
341
+ return Description(
342
+ self.name,
343
+ self.type_code,
344
+ None, # TODO: display_length; should this be self.length?
345
+ self.get_column_length(), # 'internal_size'
346
+ precision, # 'precision'
347
+ self.scale,
348
+ self.flags % 2 == 0,
349
+ self.flags,
350
+ self.charsetnr,
351
+ )
352
+
353
+ def get_column_length(self):
354
+ if self.type_code == FIELD_TYPE.VAR_STRING:
355
+ mblen = MBLENGTH.get(self.charsetnr, 1)
356
+ return self.length // mblen
357
+ return self.length
358
+
359
+ def __str__(self):
360
+ return '%s %r.%r.%r, type=%s, flags=%x, charsetnr=%s' % (
361
+ self.__class__,
362
+ self.db,
363
+ self.table_name,
364
+ self.name,
365
+ self.type_code,
366
+ self.flags,
367
+ self.charsetnr,
368
+ )
369
+
370
+
371
+ class OKPacketWrapper:
372
+ """
373
+ OK Packet Wrapper.
374
+
375
+ It uses an existing packet object, and wraps around it, exposing
376
+ useful variables while still providing access to the original packet
377
+ objects variables and methods.
378
+
379
+ """
380
+
381
+ def __init__(self, from_packet):
382
+ if not from_packet.is_ok_packet():
383
+ raise ValueError(
384
+ 'Cannot create '
385
+ + str(self.__class__.__name__)
386
+ + ' object from invalid packet type',
387
+ )
388
+
389
+ self.packet = from_packet
390
+ self.packet.advance(1)
391
+
392
+ self.affected_rows = self.packet.read_length_encoded_integer()
393
+ self.insert_id = self.packet.read_length_encoded_integer()
394
+ self.server_status, self.warning_count = self.read_struct('<HH')
395
+ self.message = self.packet.read_all()
396
+ self.has_next = self.server_status & SERVER_STATUS.SERVER_MORE_RESULTS_EXISTS
397
+
398
+ def __getattr__(self, key):
399
+ return getattr(self.packet, key)
400
+
401
+
402
+ class EOFPacketWrapper:
403
+ """
404
+ EOF Packet Wrapper.
405
+
406
+ It uses an existing packet object, and wraps around it, exposing
407
+ useful variables while still providing access to the original packet
408
+ objects variables and methods.
409
+
410
+ """
411
+
412
+ def __init__(self, from_packet):
413
+ if not from_packet.is_eof_packet():
414
+ raise ValueError(
415
+ f"Cannot create '{self.__class__}' object from invalid packet type",
416
+ )
417
+
418
+ self.packet = from_packet
419
+ self.warning_count, self.server_status = self.packet.read_struct('<xhh')
420
+ if DEBUG:
421
+ print('server_status=', self.server_status)
422
+ self.has_next = self.server_status & SERVER_STATUS.SERVER_MORE_RESULTS_EXISTS
423
+
424
+ def __getattr__(self, key):
425
+ return getattr(self.packet, key)
426
+
427
+
428
+ class LoadLocalPacketWrapper:
429
+ """
430
+ Load Local Packet Wrapper.
431
+
432
+ It uses an existing packet object, and wraps around it, exposing useful
433
+ variables while still providing access to the original packet
434
+ objects variables and methods.
435
+
436
+ """
437
+
438
+ def __init__(self, from_packet):
439
+ if not from_packet.is_load_local_packet():
440
+ raise ValueError(
441
+ f"Cannot create '{self.__class__}' object from invalid packet type",
442
+ )
443
+
444
+ self.packet = from_packet
445
+ self.filename = self.packet.get_all_data()[1:]
446
+ if DEBUG:
447
+ print('filename=', self.filename)
448
+
449
+ def __getattr__(self, key):
450
+ return getattr(self.packet, key)
@@ -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()
@@ -0,0 +1,126 @@
1
+ # type: ignore
2
+ import json
3
+ import os
4
+ import platform
5
+ import re
6
+ import unittest
7
+ import warnings
8
+
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
+ )
17
+
18
+
19
+ class PyMySQLTestCase(unittest.TestCase):
20
+ # You can specify your test environment creating a file named
21
+ # "databases.json" or editing the `databases` variable below.
22
+ fname = os.path.join(os.path.dirname(__file__), 'databases.json')
23
+ if os.path.exists(fname):
24
+ with open(fname) as f:
25
+ databases = json.load(f)
26
+ else:
27
+ params = build_params()
28
+ databases = [
29
+ {
30
+ 'host': params['host'],
31
+ 'port': params['port'],
32
+ 'user': params['user'],
33
+ 'passwd': params['password'],
34
+ 'database': DBNAME_BASE + '1',
35
+ 'use_unicode': True,
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'],
44
+ },
45
+ ]
46
+
47
+ def mysql_server_is(self, conn, version_tuple):
48
+ """
49
+ Return True if the given connection is on the version given or greater.
50
+
51
+ This only checks the server version string provided when the
52
+ connection is established, therefore any check for a version tuple
53
+ greater than (5, 5, 5) will always fail on MariaDB, as it always
54
+ starts with 5.5.5, e.g. 5.5.5-10.7.1-MariaDB-1:10.7.1+maria~focal.
55
+
56
+ Examples
57
+ --------
58
+
59
+ if self.mysql_server_is(conn, (5, 6, 4)):
60
+ # do something for MySQL 5.6.4 and above
61
+
62
+ """
63
+ server_version = conn.get_server_info()
64
+ server_version_tuple = tuple(
65
+ (int(dig) if dig is not None else 0)
66
+ for dig in re.match(r'(\d+)\.(\d+)\.(\d+)', server_version).group(1, 2, 3)
67
+ )
68
+ return server_version_tuple >= version_tuple
69
+
70
+ _connections = None
71
+
72
+ @property
73
+ def connections(self):
74
+ if self._connections is None:
75
+ self._connections = []
76
+ for params in self.databases:
77
+ conn = sv.connect(**params)
78
+ self._connections.append(conn)
79
+ self.addCleanup(self._teardown_connections)
80
+ return self._connections
81
+
82
+ def connect(self, **params):
83
+ p = self.databases[0].copy()
84
+ p.update(params)
85
+ conn = sv.connect(**p)
86
+
87
+ @self.addCleanup
88
+ def teardown():
89
+ if conn.open:
90
+ conn.close()
91
+
92
+ return conn
93
+
94
+ def _teardown_connections(self):
95
+ if self._connections:
96
+ for connection in self._connections:
97
+ if connection.open:
98
+ connection.close()
99
+ self._connections = None
100
+
101
+ def safe_create_table(self, connection, tablename, ddl, cleanup=True):
102
+ """
103
+ Create a table.
104
+
105
+ Ensures any existing version of that table is first dropped.
106
+
107
+ Also adds a cleanup rule to drop the table after the test
108
+ completes.
109
+
110
+ """
111
+ cursor = connection.cursor()
112
+
113
+ with warnings.catch_warnings():
114
+ warnings.simplefilter('ignore')
115
+ cursor.execute('drop table if exists `%s`' % (tablename,))
116
+ cursor.execute(ddl)
117
+ cursor.close()
118
+ if cleanup:
119
+ self.addCleanup(self.drop_table, connection, tablename)
120
+
121
+ def drop_table(self, connection, tablename):
122
+ cursor = connection.cursor()
123
+ with warnings.catch_warnings():
124
+ warnings.simplefilter('ignore')
125
+ cursor.execute('drop table if exists `%s`' % (tablename,))
126
+ cursor.close()