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.
- singlestoredb/__init__.py +33 -2
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/auth.py +6 -4
- singlestoredb/config.py +116 -16
- singlestoredb/connection.py +489 -523
- singlestoredb/converters.py +275 -26
- singlestoredb/exceptions.py +30 -4
- 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/__init__.py +0 -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/connection.py +1192 -0
- singlestoredb/management/__init__.py +3 -2
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +19 -14
- singlestoredb/management/manager.py +100 -40
- singlestoredb/management/organization.py +188 -0
- singlestoredb/management/region.py +6 -8
- singlestoredb/management/utils.py +253 -4
- singlestoredb/management/workspace.py +1153 -35
- singlestoredb/mysql/__init__.py +177 -0
- singlestoredb/mysql/_auth.py +298 -0
- singlestoredb/mysql/charset.py +214 -0
- singlestoredb/mysql/connection.py +1814 -0
- singlestoredb/mysql/constants/CLIENT.py +38 -0
- singlestoredb/mysql/constants/COMMAND.py +32 -0
- singlestoredb/mysql/constants/CR.py +78 -0
- singlestoredb/mysql/constants/ER.py +474 -0
- singlestoredb/mysql/constants/FIELD_TYPE.py +32 -0
- singlestoredb/mysql/constants/FLAG.py +15 -0
- singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
- singlestoredb/mysql/constants/__init__.py +0 -0
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/mysql/cursors.py +713 -0
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/mysql/optionfile.py +20 -0
- singlestoredb/mysql/protocol.py +388 -0
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/mysql/tests/base.py +126 -0
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/mysql/tests/test_DictCursor.py +132 -0
- singlestoredb/mysql/tests/test_SSCursor.py +141 -0
- singlestoredb/mysql/tests/test_basic.py +452 -0
- singlestoredb/mysql/tests/test_connection.py +851 -0
- singlestoredb/mysql/tests/test_converters.py +58 -0
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/mysql/tests/test_err.py +16 -0
- singlestoredb/mysql/tests/test_issues.py +514 -0
- singlestoredb/mysql/tests/test_load_local.py +75 -0
- singlestoredb/mysql/tests/test_nextset.py +88 -0
- singlestoredb/mysql/tests/test_optionfile.py +27 -0
- singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
- singlestoredb/mysql/times.py +23 -0
- 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 -117
- singlestoredb/tests/test_config.py +13 -15
- singlestoredb/tests/test_connection.py +241 -289
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_exceptions.py +0 -2
- 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 -28
- singlestoredb/tests/test_management.py +588 -10
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +11 -14
- singlestoredb/tests/test_types.py +0 -2
- singlestoredb/tests/test_udf.py +687 -0
- singlestoredb/tests/test_xdict.py +0 -2
- singlestoredb/tests/utils.py +3 -4
- singlestoredb/types.py +4 -5
- singlestoredb/utils/config.py +71 -12
- singlestoredb/utils/convert_rows.py +0 -2
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +4 -3
- singlestoredb/utils/xdict.py +12 -12
- singlestoredb-1.0.3.dist-info/METADATA +139 -0
- singlestoredb-1.0.3.dist-info/RECORD +112 -0
- {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/WHEEL +1 -1
- singlestoredb-1.0.3.dist-info/entry_points.txt +2 -0
- singlestoredb/drivers/__init__.py +0 -46
- singlestoredb/drivers/base.py +0 -200
- singlestoredb/drivers/cymysql.py +0 -40
- singlestoredb/drivers/http.py +0 -49
- singlestoredb/drivers/mariadb.py +0 -42
- singlestoredb/drivers/mysqlconnector.py +0 -51
- singlestoredb/drivers/mysqldb.py +0 -62
- singlestoredb/drivers/pymysql.py +0 -39
- singlestoredb/drivers/pyodbc.py +0 -67
- singlestoredb/http.py +0 -794
- singlestoredb-0.3.3.dist-info/METADATA +0 -105
- singlestoredb-0.3.3.dist-info/RECORD +0 -46
- {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/LICENSE +0 -0
- {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/top_level.txt +0 -0
singlestoredb/connection.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""SingleStoreDB connections and cursors."""
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import abc
|
|
5
4
|
import inspect
|
|
6
|
-
import pprint
|
|
7
5
|
import re
|
|
6
|
+
import warnings
|
|
8
7
|
import weakref
|
|
9
|
-
from collections import namedtuple
|
|
10
8
|
from collections.abc import Mapping
|
|
11
9
|
from collections.abc import MutableMapping
|
|
12
10
|
from typing import Any
|
|
@@ -30,23 +28,17 @@ except ImportError:
|
|
|
30
28
|
pass
|
|
31
29
|
|
|
32
30
|
from . import auth
|
|
33
|
-
from . import drivers
|
|
34
31
|
from . import exceptions
|
|
35
|
-
from . import types
|
|
36
32
|
from .config import get_option
|
|
37
|
-
from .drivers.base import Driver
|
|
38
|
-
from .utils.convert_rows import convert_row
|
|
39
|
-
from .utils.convert_rows import convert_rows
|
|
40
33
|
from .utils.results import Description
|
|
41
|
-
from .utils.results import format_results
|
|
42
34
|
from .utils.results import Result
|
|
43
35
|
|
|
44
36
|
|
|
45
37
|
# DB-API settings
|
|
46
38
|
apilevel = '2.0'
|
|
47
39
|
threadsafety = 1
|
|
48
|
-
paramstyle = map_paramstyle = '
|
|
49
|
-
positional_paramstyle = '
|
|
40
|
+
paramstyle = map_paramstyle = 'pyformat'
|
|
41
|
+
positional_paramstyle = 'format'
|
|
50
42
|
|
|
51
43
|
|
|
52
44
|
# Type codes for character-based columns
|
|
@@ -104,7 +96,7 @@ def cast_bool_param(val: Any) -> bool:
|
|
|
104
96
|
if val.lower() in ['on', 't', 'true', 'y', 'yes', 'enabled', 'enable']:
|
|
105
97
|
return True
|
|
106
98
|
elif val.lower() in ['off', 'f', 'false', 'n', 'no', 'disabled', 'disable']:
|
|
107
|
-
return
|
|
99
|
+
return False
|
|
108
100
|
|
|
109
101
|
raise ValueError('Unrecognized value for bool: {}'.format(val))
|
|
110
102
|
|
|
@@ -129,10 +121,18 @@ def build_params(**kwargs: Any) -> Dict[str, Any]:
|
|
|
129
121
|
|
|
130
122
|
# Set known parameters
|
|
131
123
|
for name in inspect.getfullargspec(connect).args:
|
|
132
|
-
if name == '
|
|
133
|
-
out[name] = kwargs.get(name,
|
|
134
|
-
elif name == 'results_format':
|
|
135
|
-
|
|
124
|
+
if name == 'conv':
|
|
125
|
+
out[name] = kwargs.get(name, None)
|
|
126
|
+
elif name == 'results_format': # deprecated
|
|
127
|
+
if kwargs.get(name, None) is not None:
|
|
128
|
+
warnings.warn(
|
|
129
|
+
'The `results_format=` parameter has been '
|
|
130
|
+
'renamed to `results_type=`.',
|
|
131
|
+
DeprecationWarning,
|
|
132
|
+
)
|
|
133
|
+
out['results_type'] = kwargs.get(name, get_option('results.type'))
|
|
134
|
+
elif name == 'results_type':
|
|
135
|
+
out[name] = kwargs.get(name, get_option('results.type'))
|
|
136
136
|
else:
|
|
137
137
|
out[name] = kwargs.get(name, get_option(name))
|
|
138
138
|
|
|
@@ -159,6 +159,9 @@ def build_params(**kwargs: Any) -> Dict[str, Any]:
|
|
|
159
159
|
if 'user' not in out and not out.get('password', None):
|
|
160
160
|
out.pop('password', None)
|
|
161
161
|
|
|
162
|
+
if out.get('ssl_ca', '') and not out.get('ssl_verify_cert', None):
|
|
163
|
+
out['ssl_verify_cert'] = True
|
|
164
|
+
|
|
162
165
|
return out
|
|
163
166
|
|
|
164
167
|
|
|
@@ -217,7 +220,8 @@ def _cast_params(params: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
217
220
|
dtype = param_types[key]
|
|
218
221
|
if dtype is bool:
|
|
219
222
|
val = cast_bool_param(val)
|
|
220
|
-
elif getattr(dtype, '_name', '') in ['Dict', 'Mapping']
|
|
223
|
+
elif getattr(dtype, '_name', '') in ['Dict', 'Mapping'] or \
|
|
224
|
+
str(dtype).startswith('typing.Dict'):
|
|
221
225
|
val = dict(val)
|
|
222
226
|
elif getattr(dtype, '_name', '') == 'List':
|
|
223
227
|
val = list(val)
|
|
@@ -303,10 +307,17 @@ def quote_identifier(name: str) -> str:
|
|
|
303
307
|
return f'`{name}`'
|
|
304
308
|
|
|
305
309
|
|
|
310
|
+
class Driver(object):
|
|
311
|
+
"""Compatibility class for driver name."""
|
|
312
|
+
|
|
313
|
+
def __init__(self, name: str):
|
|
314
|
+
self.name = name
|
|
315
|
+
|
|
316
|
+
|
|
306
317
|
class VariableAccessor(MutableMapping): # type: ignore
|
|
307
318
|
"""Variable accessor class."""
|
|
308
319
|
|
|
309
|
-
def __init__(self, conn: Connection, vtype: str):
|
|
320
|
+
def __init__(self, conn: 'Connection', vtype: str):
|
|
310
321
|
object.__setattr__(self, 'connection', weakref.proxy(conn))
|
|
311
322
|
object.__setattr__(self, 'vtype', vtype.lower())
|
|
312
323
|
if self.vtype not in [
|
|
@@ -328,30 +339,30 @@ class VariableAccessor(MutableMapping): # type: ignore
|
|
|
328
339
|
|
|
329
340
|
def __getitem__(self, name: str) -> Any:
|
|
330
341
|
name = _name_check(name)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
342
|
+
out = self.connection._iquery(
|
|
343
|
+
'show {} variables like %s;'.format(self.vtype),
|
|
344
|
+
[name],
|
|
345
|
+
)
|
|
346
|
+
if not out:
|
|
347
|
+
raise KeyError(f"No variable found with the name '{name}'.")
|
|
348
|
+
if len(out) > 1:
|
|
349
|
+
raise KeyError(f"Multiple variables found with the name '{name}'.")
|
|
350
|
+
return self._cast_value(out[0]['Value'])
|
|
339
351
|
|
|
340
352
|
def __setitem__(self, name: str, value: Any) -> None:
|
|
341
353
|
name = _name_check(name)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
'
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
cur.execute('set {} {}=:1;'.format(self.vtype, name), [value])
|
|
354
|
+
if value is True:
|
|
355
|
+
value = 'ON'
|
|
356
|
+
elif value is False:
|
|
357
|
+
value = 'OFF'
|
|
358
|
+
if 'local' in self.vtype:
|
|
359
|
+
self.connection._iquery(
|
|
360
|
+
'set {} {}=%s;'.format(
|
|
361
|
+
self.vtype.replace('local', 'session'), name,
|
|
362
|
+
), [value],
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
self.connection._iquery('set {} {}=%s;'.format(self.vtype, name), [value])
|
|
355
366
|
|
|
356
367
|
def __delitem__(self, name: str) -> None:
|
|
357
368
|
raise TypeError('Variables can not be deleted.')
|
|
@@ -366,17 +377,15 @@ class VariableAccessor(MutableMapping): # type: ignore
|
|
|
366
377
|
del self[name]
|
|
367
378
|
|
|
368
379
|
def __len__(self) -> int:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return len(list(cur))
|
|
380
|
+
out = self.connection._iquery('show {} variables;'.format(self.vtype))
|
|
381
|
+
return len(list(out))
|
|
372
382
|
|
|
373
383
|
def __iter__(self) -> Iterator[str]:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return iter(x[0] for x in list(cur))
|
|
384
|
+
out = self.connection._iquery('show {} variables;'.format(self.vtype))
|
|
385
|
+
return iter(list(x.values())[0] for x in out)
|
|
377
386
|
|
|
378
387
|
|
|
379
|
-
class Cursor(
|
|
388
|
+
class Cursor(metaclass=abc.ABCMeta):
|
|
380
389
|
"""
|
|
381
390
|
Database cursor for submitting commands and queries.
|
|
382
391
|
|
|
@@ -385,21 +394,14 @@ class Cursor(object):
|
|
|
385
394
|
|
|
386
395
|
"""
|
|
387
396
|
|
|
388
|
-
def __init__(
|
|
389
|
-
self, connection: Connection, cursor: Any, driver: Driver,
|
|
390
|
-
):
|
|
397
|
+
def __init__(self, connection: 'Connection'):
|
|
391
398
|
"""Call ``Connection.cursor`` instead."""
|
|
392
399
|
self.errorhandler = connection.errorhandler
|
|
393
|
-
self.
|
|
394
|
-
self._conn: Optional[Connection] = weakref.proxy(connection)
|
|
395
|
-
self._cursor = cursor
|
|
396
|
-
self._driver = driver
|
|
400
|
+
self._connection: Optional[Connection] = weakref.proxy(connection)
|
|
397
401
|
|
|
398
|
-
|
|
399
|
-
self.rownumber: Optional[int] = None
|
|
402
|
+
self._rownumber: Optional[int] = None
|
|
400
403
|
|
|
401
|
-
|
|
402
|
-
self.description: Optional[List[Description]] = None
|
|
404
|
+
self._description: Optional[List[Description]] = None
|
|
403
405
|
|
|
404
406
|
#: Default batch size of ``fetchmany`` calls.
|
|
405
407
|
self.arraysize = get_option('results.arraysize')
|
|
@@ -414,86 +416,32 @@ class Cursor(object):
|
|
|
414
416
|
#: Number of rows affected by the last query.
|
|
415
417
|
self.rowcount: int = -1
|
|
416
418
|
|
|
417
|
-
|
|
418
|
-
self.messages: List[str] = []
|
|
419
|
+
self._messages: List[Tuple[int, str]] = []
|
|
419
420
|
|
|
420
421
|
#: Row ID of the last modified row.
|
|
421
422
|
self.lastrowid: Optional[int] = None
|
|
422
423
|
|
|
423
424
|
@property
|
|
424
|
-
def
|
|
425
|
-
"""
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
Returns
|
|
429
|
-
-------
|
|
430
|
-
Connection or None
|
|
431
|
-
|
|
432
|
-
"""
|
|
433
|
-
return self._conn
|
|
425
|
+
def messages(self) -> List[Tuple[int, str]]:
|
|
426
|
+
"""Messages created by the server."""
|
|
427
|
+
return self._messages
|
|
434
428
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
429
|
+
@abc.abstractproperty
|
|
430
|
+
def description(self) -> Optional[List[Description]]:
|
|
431
|
+
"""The field descriptions of the last query."""
|
|
432
|
+
return self._description
|
|
438
433
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
434
|
+
@abc.abstractproperty
|
|
435
|
+
def rownumber(self) -> Optional[int]:
|
|
436
|
+
"""The last modified row number."""
|
|
437
|
+
return self._rownumber
|
|
442
438
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
for i, item in enumerate(self._cursor.description):
|
|
448
|
-
item = list(item) + [None, None]
|
|
449
|
-
item[1] = types.ColumnType.get_code(item[1])
|
|
450
|
-
item[6] = not(not(item[6]))
|
|
451
|
-
out.append(Description(*item[:9]))
|
|
452
|
-
|
|
453
|
-
# Setup override converters, if the SET flag is set use that
|
|
454
|
-
# converter but keep the same type code.
|
|
455
|
-
if item[7] and item[7] & 2048: # SET_FLAG = 2048
|
|
456
|
-
conv = self._driver.converters.get(247, None) # SET CODE = 247
|
|
457
|
-
else:
|
|
458
|
-
conv = self._driver.converters.get(item[1], None)
|
|
459
|
-
|
|
460
|
-
encoding = None
|
|
461
|
-
|
|
462
|
-
# Determine proper encoding for character fields as needed
|
|
463
|
-
if self._driver.returns_bytes:
|
|
464
|
-
if item[1] in CHAR_COLUMNS:
|
|
465
|
-
if item[8] and item[8] == 63: # BINARY / BLOB
|
|
466
|
-
pass
|
|
467
|
-
elif self._conn is not None:
|
|
468
|
-
encoding = self._conn.encoding
|
|
469
|
-
else:
|
|
470
|
-
encoding = 'utf-8'
|
|
471
|
-
elif item[1] == 16: # BIT
|
|
472
|
-
pass
|
|
473
|
-
else:
|
|
474
|
-
encoding = 'ascii'
|
|
475
|
-
|
|
476
|
-
if conv is not None:
|
|
477
|
-
self._converters.append((i, encoding, conv))
|
|
478
|
-
elif encoding is not None:
|
|
479
|
-
self._converters.append((i, encoding, None))
|
|
480
|
-
|
|
481
|
-
self.description = out
|
|
482
|
-
|
|
483
|
-
def _update_attrs(self) -> None:
|
|
484
|
-
"""Update cursor attributes from the last query."""
|
|
485
|
-
if self._cursor is None:
|
|
486
|
-
return
|
|
487
|
-
self.messages[:] = getattr(self._cursor, 'messages', [])
|
|
488
|
-
self.lastrowid = getattr(
|
|
489
|
-
self._cursor, 'lastrowid',
|
|
490
|
-
getattr(self._cursor, '_lastrowid', None),
|
|
491
|
-
) or None
|
|
492
|
-
self.rowcount = getattr(
|
|
493
|
-
self._cursor, 'rowcount',
|
|
494
|
-
getattr(self._cursor, '_rowcount', -1),
|
|
495
|
-
)
|
|
439
|
+
@property
|
|
440
|
+
def connection(self) -> Optional['Connection']:
|
|
441
|
+
"""the connection that the cursor belongs to."""
|
|
442
|
+
return self._connection
|
|
496
443
|
|
|
444
|
+
@abc.abstractmethod
|
|
497
445
|
def callproc(
|
|
498
446
|
self, name: str,
|
|
499
447
|
params: Optional[Sequence[Any]] = None,
|
|
@@ -507,18 +455,23 @@ class Cursor(object):
|
|
|
507
455
|
multiple result sets, subsequent result sets can be accessed
|
|
508
456
|
using :meth:`nextset`.
|
|
509
457
|
|
|
458
|
+
Examples
|
|
459
|
+
--------
|
|
460
|
+
>>> cur.callproc('myprocedure', ['arg1', 'arg2'])
|
|
461
|
+
>>> print(cur.fetchall())
|
|
462
|
+
|
|
510
463
|
Parameters
|
|
511
464
|
----------
|
|
512
465
|
name : str
|
|
513
466
|
Name of the stored procedure
|
|
514
|
-
params : iterable,
|
|
467
|
+
params : iterable, optional
|
|
515
468
|
Parameters to the stored procedure
|
|
516
469
|
|
|
517
470
|
"""
|
|
518
471
|
# NOTE: The `callproc` interface varies quite a bit between drivers
|
|
519
472
|
# so it is implemented using `execute` here.
|
|
520
473
|
|
|
521
|
-
if self.
|
|
474
|
+
if not self.is_connected():
|
|
522
475
|
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
523
476
|
|
|
524
477
|
name = _name_check(name)
|
|
@@ -529,186 +482,144 @@ class Cursor(object):
|
|
|
529
482
|
keys = ', '.join([f':{i+1}' for i in range(len(params))])
|
|
530
483
|
self.execute(f'CALL {name}({keys});', params)
|
|
531
484
|
|
|
485
|
+
@abc.abstractmethod
|
|
486
|
+
def is_connected(self) -> bool:
|
|
487
|
+
"""Is the cursor still connected?"""
|
|
488
|
+
raise NotImplementedError
|
|
489
|
+
|
|
490
|
+
@abc.abstractmethod
|
|
532
491
|
def close(self) -> None:
|
|
533
492
|
"""Close the cursor."""
|
|
534
|
-
|
|
535
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
536
|
-
|
|
537
|
-
try:
|
|
538
|
-
self._cursor.close()
|
|
539
|
-
|
|
540
|
-
# Ignore weak reference errors. It just means the connection
|
|
541
|
-
# was closed underneath us.
|
|
542
|
-
except ReferenceError:
|
|
543
|
-
pass
|
|
544
|
-
|
|
545
|
-
except Exception as exc:
|
|
546
|
-
raise self._driver.convert_exception(exc)
|
|
547
|
-
|
|
548
|
-
self._cursor = None
|
|
549
|
-
self._conn = None
|
|
493
|
+
raise NotImplementedError
|
|
550
494
|
|
|
495
|
+
@abc.abstractmethod
|
|
551
496
|
def execute(
|
|
552
|
-
self,
|
|
553
|
-
|
|
554
|
-
) ->
|
|
497
|
+
self, query: str,
|
|
498
|
+
args: Optional[Union[Sequence[Any], Dict[str, Any], Any]] = None,
|
|
499
|
+
) -> int:
|
|
555
500
|
"""
|
|
556
501
|
Execute a SQL statement.
|
|
557
502
|
|
|
503
|
+
Queries can use the ``format``-style parameters (``%s``) when using a
|
|
504
|
+
list of paramters or ``pyformat``-style parameters (``%(key)s``)
|
|
505
|
+
when using a dictionary of parameters.
|
|
506
|
+
|
|
558
507
|
Parameters
|
|
559
508
|
----------
|
|
560
|
-
|
|
509
|
+
query : str
|
|
561
510
|
The SQL statement to execute
|
|
562
|
-
|
|
511
|
+
args : Sequence or dict, optional
|
|
563
512
|
Parameters to substitute into the SQL code
|
|
564
513
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
514
|
+
Examples
|
|
515
|
+
--------
|
|
516
|
+
>>> cur.execute('select * from mytable')
|
|
568
517
|
|
|
569
|
-
|
|
570
|
-
self.rownumber = None
|
|
518
|
+
>>> cur.execute('select * from mytable where id < %s', [100])
|
|
571
519
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
self._driver.dbapi.paramstyle,
|
|
578
|
-
escape_char=True,
|
|
579
|
-
)
|
|
580
|
-
self._cursor.execute(*param_converter.format(oper, params))
|
|
581
|
-
else:
|
|
582
|
-
self._cursor.execute(oper)
|
|
583
|
-
except Exception as exc:
|
|
584
|
-
raise self._driver.convert_exception(exc)
|
|
520
|
+
>>> cur.execute('select * from mytable where id < %(max)s', dict(max=100))
|
|
521
|
+
|
|
522
|
+
Returns
|
|
523
|
+
-------
|
|
524
|
+
Number of rows affected
|
|
585
525
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
self.rownumber = 0
|
|
526
|
+
"""
|
|
527
|
+
raise NotImplementedError
|
|
589
528
|
|
|
590
529
|
def executemany(
|
|
591
|
-
self,
|
|
592
|
-
|
|
593
|
-
) ->
|
|
530
|
+
self, query: str,
|
|
531
|
+
args: Optional[Sequence[Union[Sequence[Any], Dict[str, Any], Any]]] = None,
|
|
532
|
+
) -> int:
|
|
594
533
|
"""
|
|
595
534
|
Execute SQL code against multiple sets of parameters.
|
|
596
535
|
|
|
536
|
+
Queries can use the ``format``-style parameters (``%s``) when using
|
|
537
|
+
lists of paramters or ``pyformat``-style parameters (``%(key)s``)
|
|
538
|
+
when using dictionaries of parameters.
|
|
539
|
+
|
|
597
540
|
Parameters
|
|
598
541
|
----------
|
|
599
|
-
|
|
542
|
+
query : str
|
|
600
543
|
The SQL statement to execute
|
|
601
|
-
|
|
544
|
+
args : iterable of iterables or dicts, optional
|
|
602
545
|
Sets of parameters to substitute into the SQL code
|
|
603
546
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
self.description = None
|
|
609
|
-
self.rownumber = None
|
|
610
|
-
|
|
611
|
-
is_dataframe = False
|
|
612
|
-
if isinstance(param_seq, DataFrame):
|
|
613
|
-
is_dataframe = True
|
|
614
|
-
else:
|
|
615
|
-
param_seq = param_seq or [[]]
|
|
616
|
-
|
|
617
|
-
try:
|
|
618
|
-
# NOTE: Just implement using `execute` to cover driver inconsistencies
|
|
619
|
-
if is_dataframe:
|
|
620
|
-
for params in param_seq.itertuples(index=False):
|
|
621
|
-
self.execute(oper, params)
|
|
547
|
+
Examples
|
|
548
|
+
--------
|
|
549
|
+
>>> cur.executemany('select * from mytable where id < %s',
|
|
550
|
+
... [[100], [200], [300]])
|
|
622
551
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
self.execute(oper, params)
|
|
626
|
-
else:
|
|
627
|
-
self.execute(oper)
|
|
552
|
+
>>> cur.executemany('select * from mytable where id < %(max)s',
|
|
553
|
+
... [dict(max=100), dict(max=100), dict(max=300)])
|
|
628
554
|
|
|
629
|
-
|
|
630
|
-
|
|
555
|
+
Returns
|
|
556
|
+
-------
|
|
557
|
+
Number of rows affected
|
|
631
558
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
559
|
+
"""
|
|
560
|
+
# NOTE: Just implement using `execute` to cover driver inconsistencies
|
|
561
|
+
if not args:
|
|
562
|
+
self.execute(query)
|
|
563
|
+
else:
|
|
564
|
+
for params in args:
|
|
565
|
+
self.execute(query, params)
|
|
566
|
+
return self.rowcount
|
|
635
567
|
|
|
568
|
+
@abc.abstractmethod
|
|
636
569
|
def fetchone(self) -> Optional[Result]:
|
|
637
570
|
"""
|
|
638
571
|
Fetch a single row from the result set.
|
|
639
572
|
|
|
573
|
+
Examples
|
|
574
|
+
--------
|
|
575
|
+
>>> while True:
|
|
576
|
+
... row = cur.fetchone()
|
|
577
|
+
... if row is None:
|
|
578
|
+
... break
|
|
579
|
+
... print(row)
|
|
580
|
+
|
|
640
581
|
Returns
|
|
641
582
|
-------
|
|
642
583
|
tuple
|
|
643
584
|
Values of the returned row if there are rows remaining
|
|
644
585
|
|
|
645
586
|
"""
|
|
646
|
-
|
|
647
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
648
|
-
|
|
649
|
-
try:
|
|
650
|
-
out = self._cursor.fetchone()
|
|
651
|
-
except Exception as exc:
|
|
652
|
-
raise self._driver.convert_exception(exc)
|
|
653
|
-
|
|
654
|
-
if out is not None and self.rownumber is not None:
|
|
655
|
-
self.rownumber += 1
|
|
656
|
-
|
|
657
|
-
if out is not None:
|
|
658
|
-
out = convert_row(tuple(out), self._converters)
|
|
659
|
-
|
|
660
|
-
return format_results(
|
|
661
|
-
self._results_format,
|
|
662
|
-
self.description or [],
|
|
663
|
-
out, single=True,
|
|
664
|
-
)
|
|
587
|
+
raise NotImplementedError
|
|
665
588
|
|
|
589
|
+
@abc.abstractmethod
|
|
666
590
|
def fetchmany(self, size: Optional[int] = None) -> Result:
|
|
667
591
|
"""
|
|
668
592
|
Fetch `size` rows from the result.
|
|
669
593
|
|
|
670
594
|
If `size` is not specified, the `arraysize` attribute is used.
|
|
671
595
|
|
|
596
|
+
Examples
|
|
597
|
+
--------
|
|
598
|
+
>>> while True:
|
|
599
|
+
... out = cur.fetchmany(100)
|
|
600
|
+
... if not len(out):
|
|
601
|
+
... break
|
|
602
|
+
... for row in out:
|
|
603
|
+
... print(row)
|
|
604
|
+
|
|
672
605
|
Returns
|
|
673
606
|
-------
|
|
674
607
|
list of tuples
|
|
675
608
|
Values of the returned rows if there are rows remaining
|
|
676
609
|
|
|
677
610
|
"""
|
|
678
|
-
|
|
679
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
680
|
-
|
|
681
|
-
if size is not None:
|
|
682
|
-
size = max(int(size), 1)
|
|
683
|
-
else:
|
|
684
|
-
size = max(int(self.arraysize), 1)
|
|
685
|
-
|
|
686
|
-
try:
|
|
687
|
-
# This is to get around a bug in mysql.connector. For some reason,
|
|
688
|
-
# fetchmany(1) returns the same row over and over again.
|
|
689
|
-
if size == 1:
|
|
690
|
-
out = [self._cursor.fetchone()]
|
|
691
|
-
else:
|
|
692
|
-
# Don't use a keyword parameter for size=. Pyodbc fails with that.
|
|
693
|
-
out = self._cursor.fetchmany(size)
|
|
694
|
-
except Exception as exc:
|
|
695
|
-
raise self._driver.convert_exception(exc)
|
|
696
|
-
|
|
697
|
-
out = convert_rows(out, self._converters)
|
|
698
|
-
|
|
699
|
-
formatted: Result = format_results(
|
|
700
|
-
self._results_format, self.description or [], out,
|
|
701
|
-
)
|
|
702
|
-
|
|
703
|
-
if self.rownumber is not None:
|
|
704
|
-
self.rownumber += len(formatted)
|
|
705
|
-
|
|
706
|
-
return formatted
|
|
611
|
+
raise NotImplementedError
|
|
707
612
|
|
|
613
|
+
@abc.abstractmethod
|
|
708
614
|
def fetchall(self) -> Result:
|
|
709
615
|
"""
|
|
710
616
|
Fetch all rows in the result set.
|
|
711
617
|
|
|
618
|
+
Examples
|
|
619
|
+
--------
|
|
620
|
+
>>> for row in cur.fetchall():
|
|
621
|
+
... print(row)
|
|
622
|
+
|
|
712
623
|
Returns
|
|
713
624
|
-------
|
|
714
625
|
list of tuples
|
|
@@ -717,29 +628,22 @@ class Cursor(object):
|
|
|
717
628
|
If there are no rows to return
|
|
718
629
|
|
|
719
630
|
"""
|
|
720
|
-
|
|
721
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
722
|
-
|
|
723
|
-
try:
|
|
724
|
-
out = self._cursor.fetchall()
|
|
725
|
-
except Exception as exc:
|
|
726
|
-
raise self._driver.convert_exception(exc)
|
|
727
|
-
|
|
728
|
-
out = convert_rows(out, self._converters)
|
|
729
|
-
|
|
730
|
-
formatted: Result = format_results(
|
|
731
|
-
self._results_format, self.description or [], out,
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
if self.rownumber is not None:
|
|
735
|
-
self.rownumber += len(formatted)
|
|
736
|
-
|
|
737
|
-
return formatted
|
|
631
|
+
raise NotImplementedError
|
|
738
632
|
|
|
633
|
+
@abc.abstractmethod
|
|
739
634
|
def nextset(self) -> Optional[bool]:
|
|
740
635
|
"""
|
|
741
636
|
Skip to the next available result set.
|
|
742
637
|
|
|
638
|
+
This is used when calling a procedure that returns multiple
|
|
639
|
+
results sets.
|
|
640
|
+
|
|
641
|
+
Note
|
|
642
|
+
----
|
|
643
|
+
The ``nextset`` method must be called until it returns an empty
|
|
644
|
+
set (i.e., once more than the number of expected result sets).
|
|
645
|
+
This is to retain compatibility with PyMySQL and MySOLdb.
|
|
646
|
+
|
|
743
647
|
Returns
|
|
744
648
|
-------
|
|
745
649
|
``True``
|
|
@@ -748,46 +652,19 @@ class Cursor(object):
|
|
|
748
652
|
If no other result set is available
|
|
749
653
|
|
|
750
654
|
"""
|
|
751
|
-
|
|
752
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
753
|
-
|
|
754
|
-
self.rownumber = None
|
|
755
|
-
|
|
756
|
-
try:
|
|
757
|
-
out = self._cursor.nextset()
|
|
758
|
-
self._set_description()
|
|
759
|
-
if out:
|
|
760
|
-
self.rownumber = 0
|
|
761
|
-
return True
|
|
762
|
-
return False
|
|
763
|
-
|
|
764
|
-
except Exception as exc:
|
|
765
|
-
exc = self._driver.convert_exception(exc)
|
|
766
|
-
if getattr(exc, 'errno', -1) == 2053:
|
|
767
|
-
return False
|
|
768
|
-
self.rownumber = 0
|
|
769
|
-
return True
|
|
655
|
+
raise NotImplementedError
|
|
770
656
|
|
|
657
|
+
@abc.abstractmethod
|
|
771
658
|
def setinputsizes(self, sizes: Sequence[int]) -> None:
|
|
772
659
|
"""Predefine memory areas for parameters."""
|
|
773
|
-
|
|
774
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
775
|
-
|
|
776
|
-
try:
|
|
777
|
-
self._cursor.setinputsizes(sizes)
|
|
778
|
-
except Exception as exc:
|
|
779
|
-
raise self._driver.convert_exception(exc)
|
|
660
|
+
raise NotImplementedError
|
|
780
661
|
|
|
662
|
+
@abc.abstractmethod
|
|
781
663
|
def setoutputsize(self, size: int, column: Optional[str] = None) -> None:
|
|
782
664
|
"""Set a column buffer size for fetches of large columns."""
|
|
783
|
-
|
|
784
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
785
|
-
|
|
786
|
-
try:
|
|
787
|
-
self._cursor.setoutputsize(size, column)
|
|
788
|
-
except Exception as exc:
|
|
789
|
-
raise self._driver.convert_exception(exc)
|
|
665
|
+
raise NotImplementedError
|
|
790
666
|
|
|
667
|
+
@abc.abstractmethod
|
|
791
668
|
def scroll(self, value: int, mode: str = 'relative') -> None:
|
|
792
669
|
"""
|
|
793
670
|
Scroll the cursor to the position in the result set.
|
|
@@ -800,21 +677,7 @@ class Cursor(object):
|
|
|
800
677
|
Where to move the cursor from: 'relative' or 'absolute'
|
|
801
678
|
|
|
802
679
|
"""
|
|
803
|
-
|
|
804
|
-
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
805
|
-
|
|
806
|
-
value = int(value)
|
|
807
|
-
try:
|
|
808
|
-
self._cursor.scroll(value, mode=mode)
|
|
809
|
-
if self.rownumber is not None:
|
|
810
|
-
if mode == 'relative':
|
|
811
|
-
self.rownumber += value
|
|
812
|
-
elif mode == 'absolute':
|
|
813
|
-
self.rownumber = value
|
|
814
|
-
else:
|
|
815
|
-
raise ValueError(f'Unrecognized scroll mode {mode}')
|
|
816
|
-
except Exception as exc:
|
|
817
|
-
raise self._driver.convert_exception(exc)
|
|
680
|
+
raise NotImplementedError
|
|
818
681
|
|
|
819
682
|
def next(self) -> Optional[Result]:
|
|
820
683
|
"""
|
|
@@ -830,18 +693,12 @@ class Cursor(object):
|
|
|
830
693
|
tuple of values
|
|
831
694
|
|
|
832
695
|
"""
|
|
833
|
-
if self.
|
|
696
|
+
if not self.is_connected():
|
|
834
697
|
raise exceptions.InterfaceError(2048, 'Cursor is closed.')
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
raise StopIteration
|
|
840
|
-
return out
|
|
841
|
-
except StopIteration:
|
|
842
|
-
raise
|
|
843
|
-
except Exception as exc:
|
|
844
|
-
raise self._driver.convert_exception(exc)
|
|
698
|
+
out = self.fetchone()
|
|
699
|
+
if out is None:
|
|
700
|
+
raise StopIteration
|
|
701
|
+
return out
|
|
845
702
|
|
|
846
703
|
__next__ = next
|
|
847
704
|
|
|
@@ -849,7 +706,7 @@ class Cursor(object):
|
|
|
849
706
|
"""Return result iterator."""
|
|
850
707
|
return self
|
|
851
708
|
|
|
852
|
-
def __enter__(self) -> Cursor:
|
|
709
|
+
def __enter__(self) -> 'Cursor':
|
|
853
710
|
"""Enter a context."""
|
|
854
711
|
return self
|
|
855
712
|
|
|
@@ -860,24 +717,22 @@ class Cursor(object):
|
|
|
860
717
|
"""Exit a context."""
|
|
861
718
|
self.close()
|
|
862
719
|
|
|
863
|
-
def is_connected(self) -> bool:
|
|
864
|
-
"""
|
|
865
|
-
Check if the cursor is connected.
|
|
866
|
-
|
|
867
|
-
Returns
|
|
868
|
-
-------
|
|
869
|
-
bool
|
|
870
|
-
|
|
871
|
-
"""
|
|
872
|
-
if self._conn is None:
|
|
873
|
-
return False
|
|
874
|
-
return self._conn.is_connected()
|
|
875
|
-
|
|
876
720
|
|
|
877
721
|
class ShowResult(Sequence[Any]):
|
|
878
722
|
"""
|
|
879
723
|
Simple result object.
|
|
880
724
|
|
|
725
|
+
This object is primarily used for displaying results to a
|
|
726
|
+
terminal or web browser, but it can also be treated like a
|
|
727
|
+
simple data frame where columns are accessible using either
|
|
728
|
+
dictionary key-like syntax or attribute syntax.
|
|
729
|
+
|
|
730
|
+
Examples
|
|
731
|
+
--------
|
|
732
|
+
>>> conn.show.status().Value[10]
|
|
733
|
+
|
|
734
|
+
>>> conn.show.status()[10]['Value']
|
|
735
|
+
|
|
881
736
|
Parameters
|
|
882
737
|
----------
|
|
883
738
|
*args : Any
|
|
@@ -885,10 +740,14 @@ class ShowResult(Sequence[Any]):
|
|
|
885
740
|
**kwargs : Any
|
|
886
741
|
Keyword parameters to send to underlying list constructor
|
|
887
742
|
|
|
743
|
+
See Also
|
|
744
|
+
--------
|
|
745
|
+
:attr:`Connection.show`
|
|
746
|
+
|
|
888
747
|
"""
|
|
889
748
|
|
|
890
749
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
891
|
-
self._data: List[Any] = []
|
|
750
|
+
self._data: List[Dict[str, Any]] = []
|
|
892
751
|
item: Any = None
|
|
893
752
|
for item in list(*args, **kwargs):
|
|
894
753
|
self._data.append(item)
|
|
@@ -897,41 +756,69 @@ class ShowResult(Sequence[Any]):
|
|
|
897
756
|
return self._data[item]
|
|
898
757
|
|
|
899
758
|
def __getattr__(self, name: str) -> List[Any]:
|
|
759
|
+
if name.startswith('_ipython'):
|
|
760
|
+
raise AttributeError(name)
|
|
900
761
|
out = []
|
|
901
762
|
for item in self._data:
|
|
902
|
-
out.append(
|
|
763
|
+
out.append(item[name])
|
|
903
764
|
return out
|
|
904
765
|
|
|
905
766
|
def __len__(self) -> int:
|
|
906
767
|
return len(self._data)
|
|
907
768
|
|
|
908
|
-
def
|
|
909
|
-
if
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
769
|
+
def __repr__(self) -> str:
|
|
770
|
+
if not self._data:
|
|
771
|
+
return ''
|
|
772
|
+
return '\n{}\n'.format(self._format_table(self._data))
|
|
773
|
+
|
|
774
|
+
@property
|
|
775
|
+
def columns(self) -> List[str]:
|
|
776
|
+
"""The columns in the result."""
|
|
777
|
+
if not self._data:
|
|
778
|
+
return []
|
|
779
|
+
return list(self._data[0].keys())
|
|
780
|
+
|
|
781
|
+
def _format_table(self, rows: Sequence[Dict[str, Any]]) -> str:
|
|
782
|
+
if not self._data:
|
|
783
|
+
return ''
|
|
784
|
+
|
|
785
|
+
keys = rows[0].keys()
|
|
786
|
+
lens = [len(x) for x in keys]
|
|
787
|
+
|
|
788
|
+
for row in self._data:
|
|
789
|
+
align = ['<'] * len(keys)
|
|
790
|
+
for i, k in enumerate(keys):
|
|
791
|
+
lens[i] = max(lens[i], len(str(row[k])))
|
|
792
|
+
align[i] = '<' if isinstance(row[k], (bytes, bytearray, str)) else '>'
|
|
793
|
+
|
|
794
|
+
fmt = '| %s |' % '|'.join([' {:%s%d} ' % (x, y) for x, y in zip(align, lens)])
|
|
795
|
+
|
|
796
|
+
out = []
|
|
797
|
+
out.append(fmt.format(*keys))
|
|
798
|
+
out.append('-' * len(out[0]))
|
|
799
|
+
for row in rows:
|
|
800
|
+
out.append(fmt.format(*[str(x) for x in row.values()]))
|
|
801
|
+
return '\n'.join(out)
|
|
802
|
+
|
|
803
|
+
def __str__(self) -> str:
|
|
804
|
+
return self.__repr__()
|
|
918
805
|
|
|
919
806
|
def _repr_html_(self) -> str:
|
|
920
807
|
if not self._data:
|
|
921
808
|
return ''
|
|
922
809
|
cell_style = 'style="text-align: left; vertical-align: top"'
|
|
923
810
|
out = []
|
|
924
|
-
out.append('<table>')
|
|
811
|
+
out.append('<table border="1" class="dataframe">')
|
|
925
812
|
out.append('<thead>')
|
|
926
813
|
out.append('<tr>')
|
|
927
|
-
for name in self._data[0].
|
|
814
|
+
for name in self._data[0].keys():
|
|
928
815
|
out.append(f'<th {cell_style}>{name}</th>')
|
|
929
816
|
out.append('</tr>')
|
|
930
817
|
out.append('</thead>')
|
|
931
818
|
out.append('<tbody>')
|
|
932
819
|
for row in self._data:
|
|
933
820
|
out.append('<tr>')
|
|
934
|
-
for item in row:
|
|
821
|
+
for item in row.values():
|
|
935
822
|
out.append(f'<td {cell_style}>{item}</td>')
|
|
936
823
|
out.append('</tr>')
|
|
937
824
|
out.append('</tbody>')
|
|
@@ -940,161 +827,188 @@ class ShowResult(Sequence[Any]):
|
|
|
940
827
|
|
|
941
828
|
|
|
942
829
|
class ShowAccessor(object):
|
|
943
|
-
"""
|
|
830
|
+
"""
|
|
831
|
+
Accessor for ``SHOW`` commands.
|
|
944
832
|
|
|
945
|
-
|
|
833
|
+
See Also
|
|
834
|
+
--------
|
|
835
|
+
:attr:`Connection.show`
|
|
836
|
+
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
def __init__(self, conn: 'Connection'):
|
|
946
840
|
self._conn = conn
|
|
947
841
|
|
|
948
842
|
def columns(self, table: str, full: bool = False) -> ShowResult:
|
|
949
843
|
"""Show the column information for the given table."""
|
|
950
844
|
table = quote_identifier(table)
|
|
951
845
|
if full:
|
|
952
|
-
return self.
|
|
953
|
-
return self.
|
|
846
|
+
return self._iquery(f'full columns in {table}')
|
|
847
|
+
return self._iquery(f'columns in {table}')
|
|
954
848
|
|
|
955
849
|
def tables(self, extended: bool = False) -> ShowResult:
|
|
956
850
|
"""Show tables in the current database."""
|
|
957
851
|
if extended:
|
|
958
|
-
return self.
|
|
959
|
-
return self.
|
|
852
|
+
return self._iquery('tables extended')
|
|
853
|
+
return self._iquery('tables')
|
|
960
854
|
|
|
961
855
|
def warnings(self) -> ShowResult:
|
|
962
856
|
"""Show warnings."""
|
|
963
|
-
return self.
|
|
857
|
+
return self._iquery('warnings')
|
|
964
858
|
|
|
965
859
|
def errors(self) -> ShowResult:
|
|
966
860
|
"""Show errors."""
|
|
967
|
-
return self.
|
|
861
|
+
return self._iquery('errors')
|
|
968
862
|
|
|
969
863
|
def databases(self, extended: bool = False) -> ShowResult:
|
|
970
864
|
"""Show all databases in the server."""
|
|
971
865
|
if extended:
|
|
972
|
-
return self.
|
|
973
|
-
return self.
|
|
866
|
+
return self._iquery('databases extended')
|
|
867
|
+
return self._iquery('databases')
|
|
974
868
|
|
|
975
869
|
def database_status(self) -> ShowResult:
|
|
976
870
|
"""Show status of the current database."""
|
|
977
|
-
return self.
|
|
871
|
+
return self._iquery('database status')
|
|
978
872
|
|
|
979
873
|
def global_status(self) -> ShowResult:
|
|
980
874
|
"""Show global status of the current server."""
|
|
981
|
-
return self.
|
|
875
|
+
return self._iquery('global status')
|
|
982
876
|
|
|
983
877
|
def indexes(self, table: str) -> ShowResult:
|
|
984
878
|
"""Show all indexes in the given table."""
|
|
985
879
|
table = quote_identifier(table)
|
|
986
|
-
return self.
|
|
880
|
+
return self._iquery(f'indexes in {table}')
|
|
987
881
|
|
|
988
882
|
def functions(self) -> ShowResult:
|
|
989
883
|
"""Show all functions in the current database."""
|
|
990
|
-
return self.
|
|
884
|
+
return self._iquery('functions')
|
|
991
885
|
|
|
992
886
|
def partitions(self, extended: bool = False) -> ShowResult:
|
|
993
887
|
"""Show partitions in the current database."""
|
|
994
888
|
if extended:
|
|
995
|
-
return self.
|
|
996
|
-
return self.
|
|
889
|
+
return self._iquery('partitions extended')
|
|
890
|
+
return self._iquery('partitions')
|
|
997
891
|
|
|
998
892
|
def pipelines(self) -> ShowResult:
|
|
999
893
|
"""Show all pipelines in the current database."""
|
|
1000
|
-
return self.
|
|
894
|
+
return self._iquery('pipelines')
|
|
1001
895
|
|
|
1002
|
-
def plan(self, plan_id:
|
|
896
|
+
def plan(self, plan_id: int, json: bool = False) -> ShowResult:
|
|
1003
897
|
"""Show the plan for the given plan ID."""
|
|
1004
|
-
plan_id =
|
|
898
|
+
plan_id = int(plan_id)
|
|
1005
899
|
if json:
|
|
1006
|
-
return self.
|
|
1007
|
-
return self.
|
|
900
|
+
return self._iquery(f'plan json {plan_id}')
|
|
901
|
+
return self._iquery(f'plan {plan_id}')
|
|
1008
902
|
|
|
1009
903
|
def plancache(self) -> ShowResult:
|
|
1010
904
|
"""Show all query statements compiled and executed."""
|
|
1011
|
-
return self.
|
|
905
|
+
return self._iquery('plancache')
|
|
1012
906
|
|
|
1013
907
|
def processlist(self) -> ShowResult:
|
|
1014
908
|
"""Show details about currently running threads."""
|
|
1015
|
-
return self.
|
|
909
|
+
return self._iquery('processlist')
|
|
1016
910
|
|
|
1017
911
|
def reproduction(self, outfile: Optional[str] = None) -> ShowResult:
|
|
1018
912
|
"""Show troubleshooting data for query optimizer and code generation."""
|
|
1019
913
|
if outfile:
|
|
1020
914
|
outfile = outfile.replace('"', r'\"')
|
|
1021
|
-
return self.
|
|
1022
|
-
return self.
|
|
915
|
+
return self._iquery('reproduction into outfile "{outfile}"')
|
|
916
|
+
return self._iquery('reproduction')
|
|
1023
917
|
|
|
1024
918
|
def schemas(self) -> ShowResult:
|
|
1025
919
|
"""Show schemas in the server."""
|
|
1026
|
-
return self.
|
|
920
|
+
return self._iquery('schemas')
|
|
1027
921
|
|
|
1028
922
|
def session_status(self) -> ShowResult:
|
|
1029
923
|
"""Show server status information for a session."""
|
|
1030
|
-
return self.
|
|
924
|
+
return self._iquery('session status')
|
|
1031
925
|
|
|
1032
926
|
def status(self, extended: bool = False) -> ShowResult:
|
|
1033
927
|
"""Show server status information."""
|
|
1034
928
|
if extended:
|
|
1035
|
-
return self.
|
|
1036
|
-
return self.
|
|
929
|
+
return self._iquery('status extended')
|
|
930
|
+
return self._iquery('status')
|
|
1037
931
|
|
|
1038
932
|
def table_status(self) -> ShowResult:
|
|
1039
933
|
"""Show table status information for the current database."""
|
|
1040
|
-
return self.
|
|
934
|
+
return self._iquery('table status')
|
|
1041
935
|
|
|
1042
936
|
def procedures(self) -> ShowResult:
|
|
1043
937
|
"""Show all procedures in the current database."""
|
|
1044
|
-
return self.
|
|
938
|
+
return self._iquery('procedures')
|
|
1045
939
|
|
|
1046
940
|
def aggregates(self) -> ShowResult:
|
|
1047
941
|
"""Show all aggregate functions in the current database."""
|
|
1048
|
-
return self.
|
|
942
|
+
return self._iquery('aggregates')
|
|
1049
943
|
|
|
1050
944
|
def create_aggregate(self, name: str) -> ShowResult:
|
|
1051
945
|
"""Show the function creation code for the given aggregate function."""
|
|
1052
946
|
name = quote_identifier(name)
|
|
1053
|
-
return self.
|
|
947
|
+
return self._iquery(f'create aggregate {name}')
|
|
1054
948
|
|
|
1055
949
|
def create_function(self, name: str) -> ShowResult:
|
|
1056
950
|
"""Show the function creation code for the given function."""
|
|
1057
951
|
name = quote_identifier(name)
|
|
1058
|
-
return self.
|
|
952
|
+
return self._iquery(f'create function {name}')
|
|
1059
953
|
|
|
1060
954
|
def create_pipeline(self, name: str, extended: bool = False) -> ShowResult:
|
|
1061
955
|
"""Show the pipeline creation code for the given pipeline."""
|
|
1062
956
|
name = quote_identifier(name)
|
|
1063
957
|
if extended:
|
|
1064
|
-
return self.
|
|
1065
|
-
return self.
|
|
958
|
+
return self._iquery(f'create pipeline {name} extended')
|
|
959
|
+
return self._iquery(f'create pipeline {name}')
|
|
1066
960
|
|
|
1067
961
|
def create_table(self, name: str) -> ShowResult:
|
|
1068
962
|
"""Show the table creation code for the given table."""
|
|
1069
963
|
name = quote_identifier(name)
|
|
1070
|
-
return self.
|
|
964
|
+
return self._iquery(f'create table {name}')
|
|
1071
965
|
|
|
1072
966
|
def create_view(self, name: str) -> ShowResult:
|
|
1073
967
|
"""Show the view creation code for the given view."""
|
|
1074
968
|
name = quote_identifier(name)
|
|
1075
|
-
return self.
|
|
1076
|
-
|
|
1077
|
-
|
|
969
|
+
return self._iquery(f'create view {name}')
|
|
970
|
+
|
|
971
|
+
# def grants(
|
|
972
|
+
# self,
|
|
973
|
+
# user: Optional[str] = None,
|
|
974
|
+
# hostname: Optional[str] = None,
|
|
975
|
+
# role: Optional[str] = None
|
|
976
|
+
# ) -> ShowResult:
|
|
977
|
+
# """Show the privileges for the given user or role."""
|
|
978
|
+
# if user:
|
|
979
|
+
# if not re.match(r'^[\w+-_]+$', user):
|
|
980
|
+
# raise ValueError(f'User name is not valid: {user}')
|
|
981
|
+
# if hostname and not re.match(r'^[\w+-_\.]+$', hostname):
|
|
982
|
+
# raise ValueError(f'Hostname is not valid: {hostname}')
|
|
983
|
+
# if hostname:
|
|
984
|
+
# return self._iquery(f"grants for '{user}@{hostname}'")
|
|
985
|
+
# return self._iquery(f"grants for '{user}'")
|
|
986
|
+
# if role:
|
|
987
|
+
# if not re.match(r'^[\w+-_]+$', role):
|
|
988
|
+
# raise ValueError(f'Role is not valid: {role}')
|
|
989
|
+
# return self._iquery(f"grants for role '{role}'")
|
|
990
|
+
# return self._iquery('grants')
|
|
991
|
+
|
|
992
|
+
def _iquery(self, qtype: str) -> ShowResult:
|
|
1078
993
|
"""Query the given object type."""
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
class Connection(object):
|
|
994
|
+
out = self._conn._iquery(f'show {qtype}')
|
|
995
|
+
for i, row in enumerate(out):
|
|
996
|
+
new_row = {}
|
|
997
|
+
for j, (k, v) in enumerate(row.items()):
|
|
998
|
+
if j == 0:
|
|
999
|
+
k = 'Name'
|
|
1000
|
+
new_row[under2camel(k)] = v
|
|
1001
|
+
out[i] = new_row
|
|
1002
|
+
return ShowResult(out)
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
class Connection(metaclass=abc.ABCMeta):
|
|
1092
1006
|
"""
|
|
1093
1007
|
SingleStoreDB connection.
|
|
1094
1008
|
|
|
1095
1009
|
Instances of this object are typically created through the
|
|
1096
1010
|
:func:`singlestoredb.connect` function rather than creating them directly.
|
|
1097
|
-
See the :func:`connect` function for parameter definitions.
|
|
1011
|
+
See the :func:`singlestoredb.connect` function for parameter definitions.
|
|
1098
1012
|
|
|
1099
1013
|
See Also
|
|
1100
1014
|
--------
|
|
@@ -1113,20 +1027,25 @@ class Connection(object):
|
|
|
1113
1027
|
ProgrammingError = exceptions.ProgrammingError
|
|
1114
1028
|
NotSupportedError = exceptions.NotSupportedError
|
|
1115
1029
|
|
|
1030
|
+
#: Read-only DB-API parameter style
|
|
1031
|
+
paramstyle = 'pyformat'
|
|
1032
|
+
|
|
1033
|
+
# Must be set by subclass
|
|
1034
|
+
driver = ''
|
|
1035
|
+
|
|
1036
|
+
# Populated when first needed
|
|
1037
|
+
_map_param_converter: Optional[sqlparams.SQLParams] = None
|
|
1038
|
+
_positional_param_converter: Optional[sqlparams.SQLParams] = None
|
|
1039
|
+
|
|
1116
1040
|
def __init__(self, **kwargs: Any):
|
|
1117
1041
|
"""Call :func:`singlestoredb.connect` instead."""
|
|
1118
|
-
self.
|
|
1042
|
+
self.connection_params: Dict[str, Any] = kwargs
|
|
1119
1043
|
self.errorhandler = None
|
|
1120
|
-
self.
|
|
1121
|
-
|
|
1122
|
-
#: Query results format ('tuple', 'namedtuple', 'dict', 'dataframe')
|
|
1123
|
-
self.results_format = self.connection_params.pop(
|
|
1124
|
-
'results_format',
|
|
1125
|
-
get_option('results.format'),
|
|
1126
|
-
)
|
|
1044
|
+
self._results_type: str = kwargs.get('results_type', None) or 'tuples'
|
|
1127
1045
|
|
|
1128
1046
|
#: Session encoding
|
|
1129
|
-
self.encoding = self.connection_params.get('charset', 'utf-8'
|
|
1047
|
+
self.encoding = self.connection_params.get('charset', None) or 'utf-8'
|
|
1048
|
+
self.encoding = self.encoding.replace('mb4', '')
|
|
1130
1049
|
|
|
1131
1050
|
# Handle various authentication types
|
|
1132
1051
|
credential_type = self.connection_params.get('credential_type', None)
|
|
@@ -1136,14 +1055,6 @@ class Connection(object):
|
|
|
1136
1055
|
self.connection_params['password'] = str(info)
|
|
1137
1056
|
self.connection_params['credential_type'] = auth.JWT
|
|
1138
1057
|
|
|
1139
|
-
drv_name = re.sub(r'^\w+\+', r'', self.connection_params['driver']).lower()
|
|
1140
|
-
self._driver = drivers.get_driver(drv_name, self.connection_params)
|
|
1141
|
-
|
|
1142
|
-
try:
|
|
1143
|
-
self._conn = self._driver.connect()
|
|
1144
|
-
except Exception as exc:
|
|
1145
|
-
raise self._driver.convert_exception(exc)
|
|
1146
|
-
|
|
1147
1058
|
#: Attribute-like access to global server variables
|
|
1148
1059
|
self.globals = VariableAccessor(self, 'global')
|
|
1149
1060
|
|
|
@@ -1162,41 +1073,93 @@ class Connection(object):
|
|
|
1162
1073
|
#: Attribute-like access to all cluster server variables
|
|
1163
1074
|
self.cluster_vars = VariableAccessor(self, 'cluster')
|
|
1164
1075
|
|
|
1076
|
+
# For backwards compatibility with SQLAlchemy package
|
|
1077
|
+
self._driver = Driver(self.driver)
|
|
1078
|
+
|
|
1079
|
+
# Output decoders
|
|
1080
|
+
self.decoders: Dict[int, Callable[[Any], Any]] = {}
|
|
1081
|
+
|
|
1082
|
+
@classmethod
|
|
1083
|
+
def _convert_params(
|
|
1084
|
+
cls, oper: str,
|
|
1085
|
+
params: Optional[Union[Sequence[Any], Dict[str, Any], Any]],
|
|
1086
|
+
) -> Tuple[Any, ...]:
|
|
1087
|
+
"""Convert query to correct parameter format."""
|
|
1088
|
+
if params:
|
|
1089
|
+
|
|
1090
|
+
if cls._map_param_converter is None:
|
|
1091
|
+
cls._map_param_converter = sqlparams.SQLParams(
|
|
1092
|
+
map_paramstyle, cls.paramstyle, escape_char=True,
|
|
1093
|
+
)
|
|
1094
|
+
|
|
1095
|
+
if cls._positional_param_converter is None:
|
|
1096
|
+
cls._positional_param_converter = sqlparams.SQLParams(
|
|
1097
|
+
positional_paramstyle, cls.paramstyle, escape_char=True,
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
is_sequence = isinstance(params, Sequence) \
|
|
1101
|
+
and not isinstance(params, str) \
|
|
1102
|
+
and not isinstance(params, bytes)
|
|
1103
|
+
is_mapping = isinstance(params, Mapping)
|
|
1104
|
+
|
|
1105
|
+
param_converter = cls._map_param_converter \
|
|
1106
|
+
if is_mapping else cls._positional_param_converter
|
|
1107
|
+
|
|
1108
|
+
if not is_sequence and not is_mapping:
|
|
1109
|
+
params = [params]
|
|
1110
|
+
|
|
1111
|
+
return param_converter.format(oper, params)
|
|
1112
|
+
|
|
1113
|
+
return (oper, None)
|
|
1114
|
+
|
|
1165
1115
|
def autocommit(self, value: bool = True) -> None:
|
|
1166
1116
|
"""Set autocommit mode."""
|
|
1167
|
-
if self._conn is None:
|
|
1168
|
-
raise exceptions.InterfaceError(2048, 'Connection is closed.')
|
|
1169
1117
|
self.locals.autocommit = bool(value)
|
|
1170
1118
|
|
|
1119
|
+
@abc.abstractmethod
|
|
1120
|
+
def connect(self) -> 'Connection':
|
|
1121
|
+
"""Connect to the server."""
|
|
1122
|
+
raise NotImplementedError
|
|
1123
|
+
|
|
1124
|
+
def _iquery(
|
|
1125
|
+
self, oper: str,
|
|
1126
|
+
params: Optional[Union[Sequence[Any], Dict[str, Any]]] = None,
|
|
1127
|
+
fix_names: bool = True,
|
|
1128
|
+
) -> List[Dict[str, Any]]:
|
|
1129
|
+
"""Return the results of a query as a list of dicts (for internal use)."""
|
|
1130
|
+
with self.cursor() as cur:
|
|
1131
|
+
cur.execute(oper, params)
|
|
1132
|
+
if not re.match(r'^\s*(select|show|call|echo)\s+', oper, flags=re.I):
|
|
1133
|
+
return []
|
|
1134
|
+
out = list(cur.fetchall())
|
|
1135
|
+
if not out:
|
|
1136
|
+
return []
|
|
1137
|
+
if isinstance(out, DataFrame):
|
|
1138
|
+
out = out.to_dict(orient='records')
|
|
1139
|
+
elif isinstance(out[0], (tuple, list)):
|
|
1140
|
+
if cur.description:
|
|
1141
|
+
names = [x[0] for x in cur.description]
|
|
1142
|
+
if fix_names:
|
|
1143
|
+
names = [under2camel(str(x).replace(' ', '')) for x in names]
|
|
1144
|
+
out = [{k: v for k, v in zip(names, row)} for row in out]
|
|
1145
|
+
return out
|
|
1146
|
+
|
|
1147
|
+
@abc.abstractmethod
|
|
1171
1148
|
def close(self) -> None:
|
|
1172
1149
|
"""Close the database connection."""
|
|
1173
|
-
|
|
1174
|
-
return None
|
|
1175
|
-
try:
|
|
1176
|
-
self._conn.close()
|
|
1177
|
-
except Exception as exc:
|
|
1178
|
-
raise self._driver.convert_exception(exc)
|
|
1179
|
-
finally:
|
|
1180
|
-
self._conn = None
|
|
1150
|
+
raise NotImplementedError
|
|
1181
1151
|
|
|
1152
|
+
@abc.abstractmethod
|
|
1182
1153
|
def commit(self) -> None:
|
|
1183
1154
|
"""Commit the pending transaction."""
|
|
1184
|
-
|
|
1185
|
-
raise exceptions.InterfaceError(2048, 'Connection is closed.')
|
|
1186
|
-
try:
|
|
1187
|
-
self._conn.commit()
|
|
1188
|
-
except Exception as exc:
|
|
1189
|
-
raise self._driver.convert_exception(exc)
|
|
1155
|
+
raise NotImplementedError
|
|
1190
1156
|
|
|
1157
|
+
@abc.abstractmethod
|
|
1191
1158
|
def rollback(self) -> None:
|
|
1192
1159
|
"""Rollback the pending transaction."""
|
|
1193
|
-
|
|
1194
|
-
raise exceptions.InterfaceError(2048, 'Connection is closed.')
|
|
1195
|
-
try:
|
|
1196
|
-
self._conn.rollback()
|
|
1197
|
-
except Exception as exc:
|
|
1198
|
-
raise self._driver.convert_exception(exc)
|
|
1160
|
+
raise NotImplementedError
|
|
1199
1161
|
|
|
1162
|
+
@abc.abstractmethod
|
|
1200
1163
|
def cursor(self) -> Cursor:
|
|
1201
1164
|
"""
|
|
1202
1165
|
Create a new cursor object.
|
|
@@ -1210,48 +1173,14 @@ class Connection(object):
|
|
|
1210
1173
|
:class:`Cursor`
|
|
1211
1174
|
|
|
1212
1175
|
"""
|
|
1213
|
-
|
|
1214
|
-
raise exceptions.InterfaceError(2048, 'Connection is closed.')
|
|
1215
|
-
try:
|
|
1216
|
-
cur = self._conn.cursor()
|
|
1217
|
-
except Exception as exc:
|
|
1218
|
-
raise self._driver.convert_exception(exc)
|
|
1219
|
-
return Cursor(self, cur, self._driver)
|
|
1220
|
-
|
|
1221
|
-
def _i_cursor(self) -> Cursor:
|
|
1222
|
-
"""
|
|
1223
|
-
Create a cursor for internal use.
|
|
1224
|
-
|
|
1225
|
-
Internal cursors always return tuples in results.
|
|
1226
|
-
These are used to ensure that methods that query the database
|
|
1227
|
-
have a consistent results structure regardless of the
|
|
1228
|
-
`results.format` option.
|
|
1229
|
-
|
|
1230
|
-
Returns
|
|
1231
|
-
-------
|
|
1232
|
-
Cursor
|
|
1233
|
-
|
|
1234
|
-
"""
|
|
1235
|
-
out = self.cursor()
|
|
1236
|
-
out._results_format = 'tuple'
|
|
1237
|
-
return out
|
|
1238
|
-
|
|
1239
|
-
@property
|
|
1240
|
-
def messages(self) -> Sequence[tuple[int, str]]:
|
|
1241
|
-
"""
|
|
1242
|
-
Return messages generated by the connection.
|
|
1243
|
-
|
|
1244
|
-
Returns
|
|
1245
|
-
-------
|
|
1246
|
-
list of tuples
|
|
1247
|
-
Each tuple contains an int code and a message
|
|
1176
|
+
raise NotImplementedError
|
|
1248
1177
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1178
|
+
@abc.abstractproperty
|
|
1179
|
+
def messages(self) -> List[Tuple[int, str]]:
|
|
1180
|
+
"""Messages generated during the connection."""
|
|
1181
|
+
raise NotImplementedError
|
|
1253
1182
|
|
|
1254
|
-
def __enter__(self) -> Connection:
|
|
1183
|
+
def __enter__(self) -> 'Connection':
|
|
1255
1184
|
"""Enter a context."""
|
|
1256
1185
|
return self
|
|
1257
1186
|
|
|
@@ -1262,6 +1191,7 @@ class Connection(object):
|
|
|
1262
1191
|
"""Exit a context."""
|
|
1263
1192
|
self.close()
|
|
1264
1193
|
|
|
1194
|
+
@abc.abstractmethod
|
|
1265
1195
|
def is_connected(self) -> bool:
|
|
1266
1196
|
"""
|
|
1267
1197
|
Determine if the database is still connected.
|
|
@@ -1271,12 +1201,7 @@ class Connection(object):
|
|
|
1271
1201
|
bool
|
|
1272
1202
|
|
|
1273
1203
|
"""
|
|
1274
|
-
|
|
1275
|
-
return False
|
|
1276
|
-
try:
|
|
1277
|
-
return self._driver.is_connected(self._conn)
|
|
1278
|
-
except Exception as exc:
|
|
1279
|
-
raise self._driver.convert_exception(exc)
|
|
1204
|
+
raise NotImplementedError
|
|
1280
1205
|
|
|
1281
1206
|
def enable_data_api(self, port: Optional[int] = None) -> int:
|
|
1282
1207
|
"""
|
|
@@ -1302,14 +1227,11 @@ class Connection(object):
|
|
|
1302
1227
|
port number of the HTTP server
|
|
1303
1228
|
|
|
1304
1229
|
"""
|
|
1305
|
-
if
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
self.globals.http_api = True
|
|
1311
|
-
cur.execute('restart proxy')
|
|
1312
|
-
return int(self.globals.http_proxy_port)
|
|
1230
|
+
if port is not None:
|
|
1231
|
+
self.globals.http_proxy_port = int(port)
|
|
1232
|
+
self.globals.http_api = True
|
|
1233
|
+
self._iquery('restart proxy')
|
|
1234
|
+
return int(self.globals.http_proxy_port)
|
|
1313
1235
|
|
|
1314
1236
|
enable_http_api = enable_data_api
|
|
1315
1237
|
|
|
@@ -1322,11 +1244,8 @@ class Connection(object):
|
|
|
1322
1244
|
:meth:`enable_data_api`
|
|
1323
1245
|
|
|
1324
1246
|
"""
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
with self._i_cursor() as cur:
|
|
1328
|
-
self.globals.http_api = False
|
|
1329
|
-
cur.execute('restart proxy')
|
|
1247
|
+
self.globals.http_api = False
|
|
1248
|
+
self._iquery('restart proxy')
|
|
1330
1249
|
|
|
1331
1250
|
disable_http_api = disable_data_api
|
|
1332
1251
|
|
|
@@ -1347,14 +1266,25 @@ def connect(
|
|
|
1347
1266
|
password: Optional[str] = None, port: Optional[int] = None,
|
|
1348
1267
|
database: Optional[str] = None, driver: Optional[str] = None,
|
|
1349
1268
|
pure_python: Optional[bool] = None, local_infile: Optional[bool] = None,
|
|
1350
|
-
|
|
1269
|
+
charset: Optional[str] = None,
|
|
1351
1270
|
ssl_key: Optional[str] = None, ssl_cert: Optional[str] = None,
|
|
1352
1271
|
ssl_ca: Optional[str] = None, ssl_disabled: Optional[bool] = None,
|
|
1353
|
-
ssl_cipher: Optional[str] = None,
|
|
1354
|
-
|
|
1355
|
-
|
|
1272
|
+
ssl_cipher: Optional[str] = None, ssl_verify_cert: Optional[bool] = None,
|
|
1273
|
+
ssl_verify_identity: Optional[bool] = None,
|
|
1274
|
+
conv: Optional[Dict[int, Callable[..., Any]]] = None,
|
|
1356
1275
|
credential_type: Optional[str] = None,
|
|
1357
1276
|
autocommit: Optional[bool] = None,
|
|
1277
|
+
results_type: Optional[str] = None,
|
|
1278
|
+
buffered: Optional[bool] = None,
|
|
1279
|
+
results_format: Optional[str] = None,
|
|
1280
|
+
program_name: Optional[str] = None,
|
|
1281
|
+
conn_attrs: Optional[Dict[str, str]] = None,
|
|
1282
|
+
multi_statements: Optional[bool] = None,
|
|
1283
|
+
connect_timeout: Optional[int] = None,
|
|
1284
|
+
nan_as_null: Optional[bool] = None,
|
|
1285
|
+
inf_as_null: Optional[bool] = None,
|
|
1286
|
+
encoding_errors: Optional[str] = None,
|
|
1287
|
+
track_env: Optional[bool] = None,
|
|
1358
1288
|
) -> Connection:
|
|
1359
1289
|
"""
|
|
1360
1290
|
Return a SingleStoreDB connection.
|
|
@@ -1364,7 +1294,7 @@ def connect(
|
|
|
1364
1294
|
host : str, optional
|
|
1365
1295
|
Hostname, IP address, or URL that describes the connection.
|
|
1366
1296
|
The scheme or protocol defines which database connector to use.
|
|
1367
|
-
By default, the ``
|
|
1297
|
+
By default, the ``mysql`` scheme is used. To connect to the
|
|
1368
1298
|
HTTP API, the scheme can be set to ``http`` or ``https``. The username,
|
|
1369
1299
|
password, host, and port are specified as in a standard URL. The path
|
|
1370
1300
|
indicates the database name. The overall form of the URL is:
|
|
@@ -1384,8 +1314,6 @@ def connect(
|
|
|
1384
1314
|
Use the connector in pure Python mode
|
|
1385
1315
|
local_infile : bool, optional
|
|
1386
1316
|
Allow local file uploads
|
|
1387
|
-
odbc_driver : str, optional
|
|
1388
|
-
Name of the ODBC driver to use for ODBC connections
|
|
1389
1317
|
charset : str, optional
|
|
1390
1318
|
Character set for string values
|
|
1391
1319
|
ssl_key : str, optional
|
|
@@ -1398,14 +1326,41 @@ def connect(
|
|
|
1398
1326
|
Sets the SSL cipher list
|
|
1399
1327
|
ssl_disabled : bool, optional
|
|
1400
1328
|
Disable SSL usage
|
|
1401
|
-
|
|
1329
|
+
ssl_verify_cert : bool, optional
|
|
1330
|
+
Verify the server's certificate. This is automatically enabled if
|
|
1331
|
+
``ssl_ca`` is also specified.
|
|
1332
|
+
ssl_verify_identity : bool, optional
|
|
1333
|
+
Verify the server's identity
|
|
1334
|
+
conv : dict[int, Callable], optional
|
|
1402
1335
|
Dictionary of data conversion functions
|
|
1403
|
-
results_format : str, optional
|
|
1404
|
-
Format of query results: tuple, namedtuple, dict, or dataframe
|
|
1405
1336
|
credential_type : str, optional
|
|
1406
1337
|
Type of authentication to use: auth.PASSWORD, auth.JWT, or auth.BROWSER_SSO
|
|
1407
1338
|
autocommit : bool, optional
|
|
1408
1339
|
Enable autocommits
|
|
1340
|
+
results_type : str, optional
|
|
1341
|
+
The form of the query results: tuples, namedtuples, dicts
|
|
1342
|
+
results_format : str, optional
|
|
1343
|
+
Deprecated. This option has been renamed to results_type.
|
|
1344
|
+
program_name : str, optional
|
|
1345
|
+
Name of the program
|
|
1346
|
+
conn_attrs : dict, optional
|
|
1347
|
+
Additional connection attributes for telemetry. Example:
|
|
1348
|
+
{'program_version': "1.0.2", "_connector_name": "dbt connector"}
|
|
1349
|
+
multi_statements: bool, optional
|
|
1350
|
+
Should multiple statements be allowed within a single query?
|
|
1351
|
+
connect_timeout : int, optional
|
|
1352
|
+
The timeout for connecting to the database in seconds.
|
|
1353
|
+
(default: 10, min: 1, max: 31536000)
|
|
1354
|
+
nan_as_null : bool, optional
|
|
1355
|
+
Should NaN values be treated as NULLs when used in parameter
|
|
1356
|
+
substitutions including uploaded data?
|
|
1357
|
+
inf_as_null : bool, optional
|
|
1358
|
+
Should Inf values be treated as NULLs when used in parameter
|
|
1359
|
+
substitutions including uploaded data?
|
|
1360
|
+
encoding_errors : str, optional
|
|
1361
|
+
The error handler name for value decoding errors
|
|
1362
|
+
track_env : bool, optional
|
|
1363
|
+
Should the connection track the SINGLESTOREDB_URL environment variable?
|
|
1409
1364
|
|
|
1410
1365
|
Examples
|
|
1411
1366
|
--------
|
|
@@ -1461,4 +1416,15 @@ def connect(
|
|
|
1461
1416
|
:class:`Connection`
|
|
1462
1417
|
|
|
1463
1418
|
"""
|
|
1464
|
-
|
|
1419
|
+
params = build_params(**dict(locals()))
|
|
1420
|
+
driver = params.get('driver', 'mysql')
|
|
1421
|
+
|
|
1422
|
+
if not driver or driver == 'mysql':
|
|
1423
|
+
from .mysql.connection import Connection # type: ignore
|
|
1424
|
+
return Connection(**params)
|
|
1425
|
+
|
|
1426
|
+
if driver in ['http', 'https']:
|
|
1427
|
+
from .http.connection import Connection
|
|
1428
|
+
return Connection(**params)
|
|
1429
|
+
|
|
1430
|
+
raise ValueError(f'Unrecognized protocol: {driver}')
|