singlestoredb 0.3.3__py3-none-any.whl → 1.0.3__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 (121) hide show
  1. singlestoredb/__init__.py +33 -2
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +6 -4
  4. singlestoredb/config.py +116 -16
  5. singlestoredb/connection.py +489 -523
  6. singlestoredb/converters.py +275 -26
  7. singlestoredb/exceptions.py +30 -4
  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/__init__.py +0 -0
  22. singlestoredb/fusion/handlers/stage.py +257 -0
  23. singlestoredb/fusion/handlers/utils.py +162 -0
  24. singlestoredb/fusion/handlers/workspace.py +412 -0
  25. singlestoredb/fusion/registry.py +164 -0
  26. singlestoredb/fusion/result.py +399 -0
  27. singlestoredb/http/__init__.py +27 -0
  28. singlestoredb/http/connection.py +1192 -0
  29. singlestoredb/management/__init__.py +3 -2
  30. singlestoredb/management/billing_usage.py +148 -0
  31. singlestoredb/management/cluster.py +19 -14
  32. singlestoredb/management/manager.py +100 -40
  33. singlestoredb/management/organization.py +188 -0
  34. singlestoredb/management/region.py +6 -8
  35. singlestoredb/management/utils.py +253 -4
  36. singlestoredb/management/workspace.py +1153 -35
  37. singlestoredb/mysql/__init__.py +177 -0
  38. singlestoredb/mysql/_auth.py +298 -0
  39. singlestoredb/mysql/charset.py +214 -0
  40. singlestoredb/mysql/connection.py +1814 -0
  41. singlestoredb/mysql/constants/CLIENT.py +38 -0
  42. singlestoredb/mysql/constants/COMMAND.py +32 -0
  43. singlestoredb/mysql/constants/CR.py +78 -0
  44. singlestoredb/mysql/constants/ER.py +474 -0
  45. singlestoredb/mysql/constants/FIELD_TYPE.py +32 -0
  46. singlestoredb/mysql/constants/FLAG.py +15 -0
  47. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  48. singlestoredb/mysql/constants/__init__.py +0 -0
  49. singlestoredb/mysql/converters.py +271 -0
  50. singlestoredb/mysql/cursors.py +713 -0
  51. singlestoredb/mysql/err.py +92 -0
  52. singlestoredb/mysql/optionfile.py +20 -0
  53. singlestoredb/mysql/protocol.py +388 -0
  54. singlestoredb/mysql/tests/__init__.py +19 -0
  55. singlestoredb/mysql/tests/base.py +126 -0
  56. singlestoredb/mysql/tests/conftest.py +37 -0
  57. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  58. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  59. singlestoredb/mysql/tests/test_basic.py +452 -0
  60. singlestoredb/mysql/tests/test_connection.py +851 -0
  61. singlestoredb/mysql/tests/test_converters.py +58 -0
  62. singlestoredb/mysql/tests/test_cursor.py +141 -0
  63. singlestoredb/mysql/tests/test_err.py +16 -0
  64. singlestoredb/mysql/tests/test_issues.py +514 -0
  65. singlestoredb/mysql/tests/test_load_local.py +75 -0
  66. singlestoredb/mysql/tests/test_nextset.py +88 -0
  67. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  68. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  69. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  70. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  71. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  72. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  73. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  74. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  75. singlestoredb/mysql/times.py +23 -0
  76. singlestoredb/pytest.py +283 -0
  77. singlestoredb/tests/empty.sql +0 -0
  78. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  79. singlestoredb/tests/test.sql +210 -0
  80. singlestoredb/tests/test2.sql +1 -0
  81. singlestoredb/tests/test_basics.py +482 -117
  82. singlestoredb/tests/test_config.py +13 -15
  83. singlestoredb/tests/test_connection.py +241 -289
  84. singlestoredb/tests/test_dbapi.py +27 -0
  85. singlestoredb/tests/test_exceptions.py +0 -2
  86. singlestoredb/tests/test_ext_func.py +1193 -0
  87. singlestoredb/tests/test_ext_func_data.py +1101 -0
  88. singlestoredb/tests/test_fusion.py +465 -0
  89. singlestoredb/tests/test_http.py +32 -28
  90. singlestoredb/tests/test_management.py +588 -10
  91. singlestoredb/tests/test_plugin.py +33 -0
  92. singlestoredb/tests/test_results.py +11 -14
  93. singlestoredb/tests/test_types.py +0 -2
  94. singlestoredb/tests/test_udf.py +687 -0
  95. singlestoredb/tests/test_xdict.py +0 -2
  96. singlestoredb/tests/utils.py +3 -4
  97. singlestoredb/types.py +4 -5
  98. singlestoredb/utils/config.py +71 -12
  99. singlestoredb/utils/convert_rows.py +0 -2
  100. singlestoredb/utils/debug.py +13 -0
  101. singlestoredb/utils/mogrify.py +151 -0
  102. singlestoredb/utils/results.py +4 -3
  103. singlestoredb/utils/xdict.py +12 -12
  104. singlestoredb-1.0.3.dist-info/METADATA +139 -0
  105. singlestoredb-1.0.3.dist-info/RECORD +112 -0
  106. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/WHEEL +1 -1
  107. singlestoredb-1.0.3.dist-info/entry_points.txt +2 -0
  108. singlestoredb/drivers/__init__.py +0 -46
  109. singlestoredb/drivers/base.py +0 -200
  110. singlestoredb/drivers/cymysql.py +0 -40
  111. singlestoredb/drivers/http.py +0 -49
  112. singlestoredb/drivers/mariadb.py +0 -42
  113. singlestoredb/drivers/mysqlconnector.py +0 -51
  114. singlestoredb/drivers/mysqldb.py +0 -62
  115. singlestoredb/drivers/pymysql.py +0 -39
  116. singlestoredb/drivers/pyodbc.py +0 -67
  117. singlestoredb/http.py +0 -794
  118. singlestoredb-0.3.3.dist-info/METADATA +0 -105
  119. singlestoredb-0.3.3.dist-info/RECORD +0 -46
  120. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/LICENSE +0 -0
  121. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/top_level.txt +0 -0
singlestoredb/http.py DELETED
@@ -1,794 +0,0 @@
1
- #!/usr/bin/env python
2
- """SingleStoreDB HTTP API interface."""
3
- from __future__ import annotations
4
-
5
- import functools
6
- import json
7
- import re
8
- from base64 import b64decode
9
- from collections.abc import Mapping
10
- from collections.abc import Sequence
11
- from typing import Any
12
- from typing import Callable
13
- from typing import Dict
14
- from typing import Iterable
15
- from typing import Optional
16
- from typing import Tuple
17
- from typing import Union
18
- from urllib.parse import urljoin
19
-
20
- import requests
21
-
22
- from . import types
23
- from .config import get_option
24
- from .converters import converters
25
- from .exceptions import DatabaseError # noqa: F401
26
- from .exceptions import DataError
27
- from .exceptions import Error
28
- from .exceptions import IntegrityError
29
- from .exceptions import InterfaceError
30
- from .exceptions import InternalError
31
- from .exceptions import NotSupportedError
32
- from .exceptions import OperationalError
33
- from .exceptions import ProgrammingError
34
- from .exceptions import Warning # noqa: F401
35
- from .utils.convert_rows import convert_rows
36
- from .utils.results import Result
37
-
38
-
39
- # DB-API settings
40
- apilevel = '2.0'
41
- paramstyle = 'qmark'
42
- threadsafety = 1
43
-
44
-
45
- Description = Tuple[
46
- str, int, Optional[int], Optional[int], Optional[int],
47
- Optional[int], bool,
48
- ]
49
-
50
-
51
- _interface_errors = set([
52
- 0,
53
- 2013, # CR_SERVER_LOST
54
- 2006, # CR_SERVER_GONE_ERROR
55
- 2012, # CR_HANDSHAKE_ERR
56
- 2004, # CR_IPSOCK_ERROR
57
- 2014, # CR_COMMANDS_OUT_OF_SYNC
58
- ])
59
- _data_errors = set([
60
- 1406, # ER_DATA_TOO_LONG
61
- 1441, # ER_DATETIME_FUNCTION_OVERFLOW
62
- 1365, # ER_DIVISION_BY_ZERO
63
- 1230, # ER_NO_DEFAULT
64
- 1171, # ER_PRIMARY_CANT_HAVE_NULL
65
- 1264, # ER_WARN_DATA_OUT_OF_RANGE
66
- 1265, # ER_WARN_DATA_TRUNCATED
67
- ])
68
- _programming_errors = set([
69
- 1065, # ER_EMPTY_QUERY
70
- 1179, # ER_CANT_DO_THIS_DURING_AN_TRANSACTION
71
- 1007, # ER_DB_CREATE_EXISTS
72
- 1110, # ER_FIELD_SPECIFIED_TWICE
73
- 1111, # ER_INVALID_GROUP_FUNC_USE
74
- 1082, # ER_NO_SUCH_INDEX
75
- 1741, # ER_NO_SUCH_KEY_VALUE
76
- 1146, # ER_NO_SUCH_TABLE
77
- 1449, # ER_NO_SUCH_USER
78
- 1064, # ER_PARSE_ERROR
79
- 1149, # ER_SYNTAX_ERROR
80
- 1113, # ER_TABLE_MUST_HAVE_COLUMNS
81
- 1112, # ER_UNSUPPORTED_EXTENSION
82
- 1102, # ER_WRONG_DB_NAME
83
- 1103, # ER_WRONG_TABLE_NAME
84
- 1049, # ER_BAD_DB_ERROR
85
- 1582, # ER_??? Wrong number of args
86
- ])
87
- _integrity_errors = set([
88
- 1215, # ER_CANNOT_ADD_FOREIGN
89
- 1062, # ER_DUP_ENTRY
90
- 1169, # ER_DUP_UNIQUE
91
- 1364, # ER_NO_DEFAULT_FOR_FIELD
92
- 1216, # ER_NO_REFERENCED_ROW
93
- 1452, # ER_NO_REFERENCED_ROW_2
94
- 1217, # ER_ROW_IS_REFERENCED
95
- 1451, # ER_ROW_IS_REFERENCED_2
96
- 1460, # ER_XAER_OUTSIDE
97
- 1401, # ER_XAER_RMERR
98
- 1048, # ER_BAD_NULL_ERROR
99
- 1264, # ER_DATA_OUT_OF_RANGE
100
- 4025, # ER_CONSTRAINT_FAILED
101
- 1826, # ER_DUP_CONSTRAINT_NAME
102
- ])
103
-
104
-
105
- def get_precision_scale(type_code: str) -> tuple[Optional[int], Optional[int]]:
106
- """Parse the precision and scale from a data type."""
107
- if '(' not in type_code:
108
- return (None, None)
109
- m = re.search(r'\(\s*(\d+)\s*,\s*(\d+)\s*\)', type_code)
110
- if m:
111
- return int(m.group(1)), int(m.group(2))
112
- m = re.search(r'\(\s*(\d+)\s*\)', type_code)
113
- if m:
114
- return (int(m.group(1)), None)
115
- raise ValueError(f'Unrecognized type code: {type_code}')
116
-
117
-
118
- def get_exc_type(code: int) -> type:
119
- """Map error code to DB-API error type."""
120
- if code in _interface_errors:
121
- return InterfaceError
122
- if code in _data_errors:
123
- return DataError
124
- if code in _programming_errors:
125
- return ProgrammingError
126
- if code in _integrity_errors:
127
- return IntegrityError
128
- if code >= 1000:
129
- return OperationalError
130
- return InternalError
131
-
132
-
133
- def identity(x: Any) -> Any:
134
- """Return input value."""
135
- return x
136
-
137
-
138
- def b64decode_converter(
139
- converter: Callable[..., Any],
140
- x: Optional[str],
141
- encoding: str = 'utf-8',
142
- ) -> Optional[bytes]:
143
- """Decode value before applying converter."""
144
- if x is None:
145
- return None
146
- if converter is None:
147
- return b64decode(x)
148
- return converter(b64decode(x))
149
-
150
-
151
- class PyMyField(object):
152
- """Field for PyMySQL compatibility."""
153
-
154
- def __init__(self, name: str, flags: int, charset: int) -> None:
155
- self.name = name
156
- self.flags = flags
157
- self.charsetnr = charset
158
-
159
-
160
- class PyMyResult(object):
161
- """Result for PyMySQL compatibility."""
162
-
163
- def __init__(self) -> None:
164
- self.fields: list[PyMyField] = []
165
-
166
- def append(self, item: PyMyField) -> None:
167
- self.fields.append(item)
168
-
169
-
170
- class Cursor(object):
171
- """
172
- SingleStoreDB HTTP database cursor.
173
-
174
- Cursor objects should not be created directly. They should come from
175
- the `cursor` method on the `Connection` object.
176
-
177
- Parameters
178
- ----------
179
- connection : Connection
180
- The HTTP Connection object the cursor belongs to
181
-
182
- """
183
-
184
- def __init__(self, connection: Connection):
185
- self.connection: Optional[Connection] = connection
186
- self._results: list[list[tuple[Any, ...]]] = [[]]
187
- self._row_idx: int = -1
188
- self._result_idx: int = -1
189
- self._descriptions: list[list[Description]] = []
190
- self.arraysize: int = 1000
191
- self.rowcount: int = 0
192
- self.messages: list[tuple[int, str]] = []
193
- self.lastrowid: Optional[int] = None
194
- self._pymy_results: list[PyMyResult] = []
195
-
196
- @property
197
- def _result(self) -> Optional[PyMyResult]:
198
- """Return Result object for PyMySQL compatibility."""
199
- if self._result_idx < 0:
200
- return None
201
- return self._pymy_results[self._result_idx]
202
-
203
- @property
204
- def description(self) -> Optional[list[Description]]:
205
- """Return description for current result set."""
206
- if not self._descriptions:
207
- return None
208
- if self._result_idx >= 0 and self._result_idx < len(self._descriptions):
209
- return self._descriptions[self._result_idx]
210
- return None
211
-
212
- def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
213
- """
214
- Invoke a POST request on the HTTP connection.
215
-
216
- Parameters
217
- ----------
218
- path : str
219
- The path of the resource
220
- *args : positional parameters, optional
221
- Extra parameters to the POST request
222
- **kwargs : keyword parameters, optional
223
- Extra keyword parameters to the POST request
224
-
225
- Returns
226
- -------
227
- requests.Response
228
-
229
- """
230
- if self.connection is None:
231
- raise InterfaceError(errno=2048, msg='Connection is closed.')
232
- return self.connection._post(path, *args, **kwargs)
233
-
234
- def callproc(
235
- self, name: str,
236
- params: Union[Sequence[Any], Mapping[str, Any]],
237
- ) -> None:
238
- """
239
- Call a stored procedure.
240
-
241
- Parameters
242
- ----------
243
- name : str
244
- Name of the stored procedure
245
- params : iterable or dict, optional
246
- Parameters to the stored procedure
247
-
248
- """
249
- if self.connection is None:
250
- raise InterfaceError(errno=2048, msg='Connection is closed.')
251
- raise NotImplementedError
252
-
253
- def close(self) -> None:
254
- """Close the cursor."""
255
- if self.connection is not None:
256
- self.connection = None
257
-
258
- def execute(
259
- self, query: str,
260
- params: Optional[Union[Sequence[Any], Mapping[str, Any]]] = None,
261
- ) -> None:
262
- """
263
- Execute a SQL statement.
264
-
265
- Parameters
266
- ----------
267
- oper : str
268
- The SQL statement to execute
269
- params : iterable or dict, optional
270
- Parameters to substitute into the SQL code
271
-
272
- """
273
- if self.connection is None:
274
- raise InterfaceError(errno=2048, msg='Connection is closed.')
275
-
276
- data: Dict[str, Any] = dict(sql=query)
277
- if params is not None:
278
- data['args'] = params
279
- if self.connection._database:
280
- data['database'] = self.connection._database
281
-
282
- sql_type = 'exec'
283
- if re.match(r'^\s*(select|show|call|echo)\s+', query, flags=re.I):
284
- sql_type = 'query'
285
-
286
- if sql_type == 'query':
287
- res = self._post('query/tuples', json=data)
288
- else:
289
- res = self._post('exec', json=data)
290
-
291
- if res.status_code >= 400:
292
- if res.text:
293
- if re.match(r'^Error\s+\d+:', res.text):
294
- code, msg = res.text.split(':', 1)
295
- icode = int(code.split()[-1])
296
- else:
297
- icode = res.status_code
298
- msg = res.text
299
- raise get_exc_type(icode)(icode, msg.strip())
300
- raise InterfaceError(errno=res.status_code, msg='HTTP Error')
301
-
302
- out = json.loads(res.text)
303
-
304
- self._descriptions = []
305
- self._results = []
306
- self._row_idx = -1
307
- self._result_idx = -1
308
- self.rowcount = 0
309
-
310
- if sql_type == 'query':
311
- # description: (name, type_code, display_size, internal_size,
312
- # precision, scale, null_ok, column_flags, charset)
313
-
314
- # Remove converters for things the JSON parser already converted
315
- http_converters = dict(converters)
316
- http_converters.pop(4, None)
317
- http_converters.pop(5, None)
318
- http_converters.pop(6, None)
319
- http_converters.pop(15, None)
320
- http_converters.pop(245, None)
321
- http_converters.pop(247, None)
322
- http_converters.pop(249, None)
323
- http_converters.pop(250, None)
324
- http_converters.pop(251, None)
325
- http_converters.pop(252, None)
326
- http_converters.pop(253, None)
327
- http_converters.pop(254, None)
328
-
329
- results = out['results']
330
-
331
- # Convert data to Python types
332
- if results and results[0]:
333
- self._row_idx = 0
334
- self._result_idx = 0
335
-
336
- for result in results:
337
-
338
- pymy_res = PyMyResult()
339
- convs = []
340
-
341
- description: list[Description] = []
342
- for i, col in enumerate(result.get('columns', [])):
343
- charset = 0
344
- flags = 0
345
- data_type = col['dataType'].split('(')[0]
346
- type_code = types.ColumnType.get_code(data_type)
347
- prec, scale = get_precision_scale(col['dataType'])
348
- converter = http_converters.get(type_code, None)
349
- if 'UNSIGNED' in data_type:
350
- flags = 32
351
- if data_type.endswith('BLOB') or data_type.endswith('BINARY'):
352
- converter = functools.partial(b64decode_converter, converter)
353
- charset = 63 # BINARY
354
- if type_code == 0: # DECIMAL
355
- type_code = types.ColumnType.get_code('NEWDECIMAL')
356
- elif type_code == 15: # VARCHAR / VARBINARY
357
- type_code = types.ColumnType.get_code('VARSTRING')
358
- if type_code == 246 and prec is not None: # NEWDECIMAL
359
- prec += 1 # for sign
360
- if scale is not None and scale > 0:
361
- prec += 1 # for decimal
362
- if converter is not None:
363
- convs.append((i, None, converter))
364
- description.append((
365
- str(col['name']), type_code,
366
- None, None, prec, scale,
367
- col.get('nullable', False),
368
- ))
369
- pymy_res.append(PyMyField(col['name'], flags, charset))
370
- self._descriptions.append(description)
371
-
372
- rows = convert_rows(result.get('rows', []), convs)
373
-
374
- self._results.append(rows)
375
- self._pymy_results.append(pymy_res)
376
-
377
- self.rowcount = len(self._results[0])
378
- else:
379
- self.rowcount = out['rowsAffected']
380
-
381
- def executemany(
382
- self, query: str,
383
- param_seq: Optional[
384
- Sequence[
385
- Union[
386
- Sequence[Any],
387
- Mapping[str, Any],
388
- ]
389
- ]
390
- ] = None,
391
- ) -> None:
392
- """
393
- Execute SQL code against multiple sets of parameters.
394
-
395
- Parameters
396
- ----------
397
- query : str
398
- The SQL statement to execute
399
- params_seq : iterable of iterables or dicts, optional
400
- Sets of parameters to substitute into the SQL code
401
-
402
- """
403
- if self.connection is None:
404
- raise InterfaceError(errno=2048, msg='Connection is closed.')
405
-
406
- results = []
407
- if param_seq:
408
- description = []
409
- for params in param_seq:
410
- self.execute(query, params)
411
- if self._descriptions:
412
- description = self._descriptions[-1]
413
- if self._rows is not None:
414
- results.append(self._rows)
415
- self._results = results
416
- self._descriptions = [description for _ in range(len(results))]
417
- if self._results:
418
- self.rowcount = len(self._results[0])
419
- else:
420
- self.execute(query)
421
-
422
- @property
423
- def _has_row(self) -> bool:
424
- """Determine if a row is available."""
425
- if self._result_idx < 0 or self._result_idx >= len(self._results):
426
- return False
427
- if self._row_idx < 0 or self._row_idx >= len(self._results[self._result_idx]):
428
- return False
429
- return True
430
-
431
- @property
432
- def _rows(self) -> list[tuple[Any, ...]]:
433
- """Return current set of rows."""
434
- if not self._has_row:
435
- return []
436
- return self._results[self._result_idx]
437
-
438
- def fetchone(self) -> Optional[Result]:
439
- """
440
- Fetch a single row from the result set.
441
-
442
- Returns
443
- -------
444
- tuple
445
- Values of the returned row if there are rows remaining
446
- None
447
- If there are no rows left to return
448
-
449
- """
450
- if not self._has_row:
451
- return None
452
- out = self._rows[self._row_idx]
453
- self._row_idx += 1
454
- return out
455
-
456
- def fetchmany(
457
- self,
458
- size: Optional[int] = None,
459
- ) -> Result:
460
- """
461
- Fetch `size` rows from the result.
462
-
463
- If `size` is not specified, the `arraysize` attribute is used.
464
-
465
- Returns
466
- -------
467
- list of tuples
468
- Values of the returned rows if there are rows remaining
469
-
470
- """
471
- if not self._has_row:
472
- return []
473
- if not size:
474
- size = max(int(self.arraysize), 1)
475
- else:
476
- size = max(int(size), 1)
477
- out = self._rows[self._row_idx:self._row_idx+size]
478
- self._row_idx += size
479
- return out
480
-
481
- def fetchall(self) -> Result:
482
- """
483
- Fetch all rows in the result set.
484
-
485
- Returns
486
- -------
487
- list of tuples
488
- Values of the returned rows if there are rows remaining
489
-
490
- """
491
- if not self._has_row:
492
- return []
493
- out = list(self._rows)
494
- self._row_idx = -1
495
- return out
496
-
497
- def nextset(self) -> Optional[bool]:
498
- """Skip to the next available result set."""
499
- if self._result_idx < 0:
500
- self._row_idx = -1
501
- return False
502
-
503
- self._result_idx += 1
504
- self._row_idx = 0
505
-
506
- if self._result_idx >= len(self._results):
507
- self._result_idx = -1
508
- self._row_idx = -1
509
- return False
510
-
511
- self.rowcount = len(self._results[self._result_idx])
512
-
513
- return True
514
-
515
- def setinputsizes(self, sizes: Sequence[int]) -> None:
516
- """Predefine memory areas for parameters."""
517
- pass
518
-
519
- def setoutputsize(self, size: int, column: Optional[str] = None) -> None:
520
- """Set a column buffer size for fetches of large columns."""
521
- pass
522
-
523
- @ property
524
- def rownumber(self) -> Optional[int]:
525
- """
526
- Return the zero-based index of the cursor in the result set.
527
-
528
- Returns
529
- -------
530
- int
531
-
532
- """
533
- if self._row_idx < 0:
534
- return None
535
- return self._row_idx
536
-
537
- def scroll(self, value: int, mode: str = 'relative') -> None:
538
- """
539
- Scroll the cursor to the position in the result set.
540
-
541
- Parameters
542
- ----------
543
- value : int
544
- Value of the positional move
545
- mode : str
546
- Type of move that should be made: 'relative' or 'absolute'
547
-
548
- """
549
- if mode == 'relative':
550
- self._row_idx += value
551
- elif mode == 'absolute':
552
- self._row_idx = value
553
- else:
554
- raise ValueError(
555
- f'{mode} is not a valid mode, '
556
- 'expecting "relative" or "absolute"',
557
- )
558
-
559
- def next(self) -> Optional[Result]:
560
- """
561
- Return the next row from the result set for use in iterators.
562
-
563
- Returns
564
- -------
565
- tuple
566
- Values from the next result row
567
- None
568
- If no more rows exist
569
-
570
- """
571
- out = self.fetchone()
572
- if out is None:
573
- raise StopIteration
574
- return out
575
-
576
- __next__ = next
577
-
578
- def __iter__(self) -> Iterable[tuple[Any, ...]]:
579
- """Return result iterator."""
580
- return iter(self._rows)
581
-
582
- def __enter__(self) -> Cursor:
583
- """Enter a context."""
584
- return self
585
-
586
- def __exit__(
587
- self, exc_type: Optional[object],
588
- exc_value: Optional[Exception], exc_traceback: Optional[str],
589
- ) -> None:
590
- """Exit a context."""
591
- self.close()
592
-
593
- def is_connected(self) -> bool:
594
- """
595
- Check if the cursor is still connected.
596
-
597
- Returns
598
- -------
599
- bool
600
-
601
- """
602
- if self.connection is None:
603
- return False
604
- return self.connection.is_connected()
605
-
606
-
607
- class Connection(object):
608
- """
609
- SingleStoreDB HTTP database connection.
610
-
611
- Instances of this object are typically created through the
612
- `connection` function rather than creating them directly.
613
-
614
- See Also
615
- --------
616
- `connect`
617
-
618
- """
619
-
620
- Warning = Warning
621
- Error = Error
622
- InterfaceError = InterfaceError
623
- DatabaseError = DatabaseError
624
- DataError = DataError
625
- OperationalError = OperationalError
626
- IntegrityError = IntegrityError
627
- InternalError = InternalError
628
- ProgrammingError = ProgrammingError
629
- NotSupportedError = NotSupportedError
630
-
631
- def __init__(self, **kwargs: Any):
632
- host = kwargs.get('host', get_option('host'))
633
- port = kwargs.get('port', get_option('http_port'))
634
-
635
- self._sess: Optional[requests.Session] = requests.Session()
636
-
637
- user = kwargs.get('user', get_option('user'))
638
- password = kwargs.get('password', get_option('password'))
639
- if user is not None and password is not None:
640
- self._sess.auth = (user, password)
641
- elif user is not None:
642
- self._sess.auth = (user, '')
643
- self._sess.headers.update({
644
- 'Content-Type': 'application/json',
645
- 'Accept': 'application/json',
646
- 'Accept-Encoding': 'compress,identity',
647
- })
648
-
649
- if kwargs.get('ssl_disabled', get_option('ssl_disabled')):
650
- self._sess.verify = False
651
- else:
652
- ssl_key = kwargs.get('ssl_key', get_option('ssl_key'))
653
- ssl_cert = kwargs.get('ssl_cert', get_option('ssl_cert'))
654
- if ssl_key and ssl_cert:
655
- self._sess.cert = (ssl_key, ssl_cert)
656
- elif ssl_cert:
657
- self._sess.cert = ssl_cert
658
-
659
- ssl_ca = kwargs.get('ssl_ca', get_option('ssl_ca'))
660
- if ssl_ca:
661
- self._sess.verify = ssl_ca
662
-
663
- version = kwargs.get('version', 'v1')
664
- protocol = kwargs.get('protocol', 'https')
665
-
666
- self._database = kwargs.get('database', get_option('database'))
667
- self._url = f'{protocol}://{host}:{port}/api/{version}/'
668
- self.messages: list[list[Any]] = []
669
- self._autocommit: bool = True
670
-
671
- def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
672
- """
673
- Invoke a POST request on the HTTP connection.
674
-
675
- Parameters
676
- ----------
677
- path : str
678
- The path of the resource
679
- *args : positional parameters, optional
680
- Extra parameters to the POST request
681
- **kwargs : keyword parameters, optional
682
- Extra keyword parameters to the POST request
683
-
684
- Returns
685
- -------
686
- requests.Response
687
-
688
- """
689
- if self._sess is None:
690
- raise InterfaceError(errno=2048, msg='Connection is closed.')
691
- return self._sess.post(urljoin(self._url, path), *args, **kwargs)
692
-
693
- def close(self) -> None:
694
- """Close the connection."""
695
- self._sess = None
696
-
697
- def autocommit(self, value: bool) -> None:
698
- """Set autocommit mode."""
699
- self._autocommit = value
700
-
701
- def commit(self) -> None:
702
- """Commit the pending transaction."""
703
- if self._autocommit:
704
- return
705
- raise NotSupportedError(msg='operation not supported')
706
-
707
- def rollback(self) -> None:
708
- """Rollback the pending transaction."""
709
- if self._autocommit:
710
- return
711
- raise NotSupportedError(msg='operation not supported')
712
-
713
- def cursor(self) -> Cursor:
714
- """
715
- Create a new cursor object.
716
-
717
- Returns
718
- -------
719
- Cursor
720
-
721
- """
722
- return Cursor(self)
723
-
724
- def __enter__(self) -> Connection:
725
- """Enter a context."""
726
- return self
727
-
728
- def __exit__(
729
- self, exc_type: Optional[object],
730
- exc_value: Optional[Exception], exc_traceback: Optional[str],
731
- ) -> None:
732
- """Exit a context."""
733
- self.close()
734
-
735
- def is_connected(self) -> bool:
736
- """
737
- Check if the database is still connected.
738
-
739
- Returns
740
- -------
741
- bool
742
-
743
- """
744
- if self._sess is None:
745
- return False
746
- url = '/'.join(self._url.split('/')[:3]) + '/ping'
747
- res = self._sess.get(url)
748
- if res.status_code <= 400 and res.text == 'pong':
749
- return True
750
- return False
751
-
752
-
753
- def connect(
754
- host: Optional[str] = None, port: Optional[int] = None,
755
- user: Optional[str] = None, password: Optional[str] = None,
756
- database: Optional[str] = None, protocol: str = 'https', version: str = 'v1',
757
- ssl_key: Optional[str] = None, ssl_cert: Optional[str] = None,
758
- ssl_ca: Optional[str] = None, ssl_disabled: Optional[bool] = None,
759
- ) -> Connection:
760
- """
761
- Connect to a SingleStoreDB using HTTP.
762
-
763
- Parameters
764
- ----------
765
- user : str, optional
766
- Database user name
767
- password : str, optional
768
- Database user password
769
- host : str, optional
770
- Database host name or IP address
771
- port : int, optional
772
- Database port. This defaults to 3306 for non-HTTP connections, 80
773
- for HTTP connections, and 443 for HTTPS connections.
774
- database : str, optional
775
- Database name
776
- protocol : str, optional
777
- HTTP protocol: `http` or `https`
778
- version : str, optional
779
- Version of the HTTP API
780
- ssl_key : str, optional
781
- File containing SSL key
782
- ssl_cert : str, optional
783
- File containing SSL certificate
784
- ssl_ca : str, optional
785
- File containing SSL certificate authority
786
- ssl_disabled : bool, optional
787
- Disable SSL usage
788
-
789
- Returns
790
- -------
791
- Connection
792
-
793
- """
794
- return Connection(**dict(locals()))