singlestoredb 0.4.0__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- singlestoredb/__init__.py +33 -1
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/auth.py +5 -1
- singlestoredb/config.py +116 -14
- singlestoredb/connection.py +483 -516
- singlestoredb/converters.py +238 -135
- singlestoredb/exceptions.py +30 -2
- singlestoredb/functions/__init__.py +1 -0
- singlestoredb/functions/decorator.py +142 -0
- singlestoredb/functions/dtypes.py +1639 -0
- singlestoredb/functions/ext/__init__.py +2 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +661 -0
- singlestoredb/functions/ext/json.py +427 -0
- singlestoredb/functions/ext/mmap.py +306 -0
- singlestoredb/functions/ext/rowdat_1.py +744 -0
- singlestoredb/functions/signature.py +673 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +621 -0
- singlestoredb/fusion/handlers/stage.py +257 -0
- singlestoredb/fusion/handlers/utils.py +162 -0
- singlestoredb/fusion/handlers/workspace.py +412 -0
- singlestoredb/fusion/registry.py +164 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/{http.py → http/connection.py} +555 -154
- singlestoredb/management/__init__.py +3 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +14 -6
- singlestoredb/management/manager.py +100 -38
- singlestoredb/management/organization.py +188 -0
- singlestoredb/management/region.py +5 -5
- singlestoredb/management/utils.py +281 -2
- singlestoredb/management/workspace.py +1344 -49
- singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
- singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
- singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
- singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
- singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
- singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
- singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
- singlestoredb/pytest.py +283 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +385 -0
- singlestoredb/tests/test.sql +210 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +482 -115
- singlestoredb/tests/test_config.py +13 -13
- singlestoredb/tests/test_connection.py +241 -305
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_ext_func.py +1193 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +465 -0
- singlestoredb/tests/test_http.py +32 -26
- singlestoredb/tests/test_management.py +588 -8
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +11 -12
- singlestoredb/tests/test_udf.py +687 -0
- singlestoredb/tests/utils.py +3 -2
- singlestoredb/utils/config.py +58 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +4 -1
- singlestoredb-1.0.4.dist-info/METADATA +139 -0
- singlestoredb-1.0.4.dist-info/RECORD +112 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
- singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
- singlestoredb/clients/pymysqlsv/converters.py +0 -365
- singlestoredb/clients/pymysqlsv/err.py +0 -144
- singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
- singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
- singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
- singlestoredb/drivers/__init__.py +0 -45
- singlestoredb/drivers/base.py +0 -198
- singlestoredb/drivers/cymysql.py +0 -38
- singlestoredb/drivers/http.py +0 -47
- singlestoredb/drivers/mariadb.py +0 -40
- singlestoredb/drivers/mysqlconnector.py +0 -49
- singlestoredb/drivers/mysqldb.py +0 -60
- singlestoredb/drivers/pymysql.py +0 -37
- singlestoredb/drivers/pymysqlsv.py +0 -35
- singlestoredb/drivers/pyodbc.py +0 -65
- singlestoredb-0.4.0.dist-info/METADATA +0 -111
- singlestoredb-0.4.0.dist-info/RECORD +0 -86
- /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
- /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/top_level.txt +0 -0
|
@@ -13,11 +13,9 @@ import traceback
|
|
|
13
13
|
import warnings
|
|
14
14
|
|
|
15
15
|
try:
|
|
16
|
-
import
|
|
16
|
+
import _singlestoredb_accel
|
|
17
17
|
except (ImportError, ModuleNotFoundError):
|
|
18
|
-
|
|
19
|
-
# warnings.warn('Accelerator extension could not be loaded; '
|
|
20
|
-
# 'running in pure Python mode.', RuntimeWarning)
|
|
18
|
+
_singlestoredb_accel = None
|
|
21
19
|
|
|
22
20
|
from . import _auth
|
|
23
21
|
|
|
@@ -26,11 +24,17 @@ from .constants import CLIENT, COMMAND, CR, ER, FIELD_TYPE, SERVER_STATUS
|
|
|
26
24
|
from . import converters
|
|
27
25
|
from .cursors import (
|
|
28
26
|
Cursor,
|
|
29
|
-
|
|
27
|
+
CursorSV,
|
|
30
28
|
DictCursor,
|
|
29
|
+
DictCursorSV,
|
|
30
|
+
NamedtupleCursor,
|
|
31
|
+
NamedtupleCursorSV,
|
|
32
|
+
SSCursor,
|
|
33
|
+
SSCursorSV,
|
|
31
34
|
SSDictCursor,
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
SSDictCursorSV,
|
|
36
|
+
SSNamedtupleCursor,
|
|
37
|
+
SSNamedtupleCursorSV,
|
|
34
38
|
)
|
|
35
39
|
from .optionfile import Parser
|
|
36
40
|
from .protocol import (
|
|
@@ -41,7 +45,12 @@ from .protocol import (
|
|
|
41
45
|
EOFPacketWrapper,
|
|
42
46
|
LoadLocalPacketWrapper,
|
|
43
47
|
)
|
|
44
|
-
from . import err
|
|
48
|
+
from . import err
|
|
49
|
+
from ..config import get_option
|
|
50
|
+
from .. import fusion
|
|
51
|
+
from .. import connection
|
|
52
|
+
from ..connection import Connection as BaseConnection
|
|
53
|
+
from ..utils.debug import log_query
|
|
45
54
|
|
|
46
55
|
try:
|
|
47
56
|
import ssl
|
|
@@ -60,7 +69,7 @@ except (ImportError, KeyError):
|
|
|
60
69
|
# KeyError occurs when there's no entry in OS database for a current user.
|
|
61
70
|
DEFAULT_USER = None
|
|
62
71
|
|
|
63
|
-
DEBUG =
|
|
72
|
+
DEBUG = get_option('debug.connection')
|
|
64
73
|
|
|
65
74
|
TEXT_TYPES = {
|
|
66
75
|
FIELD_TYPE.BIT,
|
|
@@ -106,87 +115,149 @@ def _lenenc_int(i):
|
|
|
106
115
|
)
|
|
107
116
|
|
|
108
117
|
|
|
109
|
-
class Connection:
|
|
118
|
+
class Connection(BaseConnection):
|
|
110
119
|
"""
|
|
111
120
|
Representation of a socket with a mysql server.
|
|
112
121
|
|
|
113
122
|
The proper way to get an instance of this class is to call
|
|
114
|
-
connect()
|
|
115
|
-
|
|
116
|
-
Establish a connection to the
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
``connect()``.
|
|
124
|
+
|
|
125
|
+
Establish a connection to the SingleStoreDB database.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
host : str, optional
|
|
130
|
+
Host where the database server is located.
|
|
131
|
+
user : str, optional
|
|
132
|
+
Username to log in as.
|
|
133
|
+
password : str, optional
|
|
134
|
+
Password to use.
|
|
135
|
+
database : str, optional
|
|
136
|
+
Database to use, None to not use a particular one.
|
|
137
|
+
port : int, optional
|
|
138
|
+
Server port to use, default is usually OK. (default: 3306)
|
|
139
|
+
bind_address : str, optional
|
|
140
|
+
When the client has multiple network interfaces, specify
|
|
125
141
|
the interface from which to connect to the host. Argument can be
|
|
126
142
|
a hostname or an IP address.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
:
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
unix_socket : str, optional
|
|
144
|
+
Use a unix socket rather than TCP/IP.
|
|
145
|
+
read_timeout : int, optional
|
|
146
|
+
The timeout for reading from the connection in seconds
|
|
147
|
+
(default: None - no timeout)
|
|
148
|
+
write_timeout : int, optional
|
|
149
|
+
The timeout for writing to the connection in seconds
|
|
150
|
+
(default: None - no timeout)
|
|
151
|
+
charset : str, optional
|
|
152
|
+
Charset to use.
|
|
153
|
+
collation : str, optional
|
|
154
|
+
The charset collation
|
|
155
|
+
sql_mode : str, optional
|
|
156
|
+
Default SQL_MODE to use.
|
|
157
|
+
read_default_file : str, optional
|
|
158
|
+
Specifies my.cnf file to read these parameters from under the
|
|
159
|
+
[client] section.
|
|
160
|
+
conv : Dict[str, Callable[Any]], optional
|
|
135
161
|
Conversion dictionary to use instead of the default one.
|
|
136
162
|
This is used to provide custom marshalling and unmarshalling of types.
|
|
137
163
|
See converters.
|
|
138
|
-
:
|
|
164
|
+
use_unicode : bool, optional
|
|
139
165
|
Whether or not to default to unicode strings.
|
|
140
166
|
This option defaults to true.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
client_flag : int, optional
|
|
168
|
+
Custom flags to send to MySQL. Find potential values in constants.CLIENT.
|
|
169
|
+
cursorclass : type, optional
|
|
170
|
+
Custom cursor class to use.
|
|
171
|
+
init_command : str, optional
|
|
172
|
+
Initial SQL statement to run when connection is established.
|
|
173
|
+
connect_timeout : int, optional
|
|
174
|
+
The timeout for connecting to the database in seconds.
|
|
145
175
|
(default: 10, min: 1, max: 31536000)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
176
|
+
ssl : Dict[str, str], optional
|
|
177
|
+
A dict of arguments similar to mysql_ssl_set()'s parameters or
|
|
178
|
+
an ssl.SSLContext.
|
|
179
|
+
ssl_ca : str, optional
|
|
180
|
+
Path to the file that contains a PEM-formatted CA certificate.
|
|
181
|
+
ssl_cert : str, optional
|
|
182
|
+
Path to the file that contains a PEM-formatted client certificate.
|
|
183
|
+
ssl_cipher : str, optional
|
|
184
|
+
SSL ciphers to allow.
|
|
185
|
+
ssl_disabled : bool, optional
|
|
186
|
+
A boolean value that disables usage of TLS.
|
|
187
|
+
ssl_key : str, optional
|
|
188
|
+
Path to the file that contains a PEM-formatted private key for the
|
|
189
|
+
client certificate.
|
|
190
|
+
ssl_verify_cert : str, optional
|
|
191
|
+
Set to true to check the server certificate's validity.
|
|
192
|
+
ssl_verify_identity : bool, optional
|
|
193
|
+
Set to true to check the server's identity.
|
|
194
|
+
read_default_group : str, optional
|
|
195
|
+
Group to read from in the configuration file.
|
|
196
|
+
autocommit : bool, optional
|
|
197
|
+
Autocommit mode. None means use server default. (default: False)
|
|
198
|
+
local_infile : bool, optional
|
|
199
|
+
Boolean to enable the use of LOAD DATA LOCAL command. (default: False)
|
|
200
|
+
max_allowed_packet : int, optional
|
|
201
|
+
Max size of packet sent to server in bytes. (default: 16MB)
|
|
202
|
+
Only used to limit size of "LOAD LOCAL INFILE" data packet smaller
|
|
203
|
+
than default (16KB).
|
|
204
|
+
defer_connect : bool, optional
|
|
205
|
+
Don't explicitly connect on construction - wait for connect call.
|
|
159
206
|
(default: False)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
The class
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
207
|
+
auth_plugin_map : Dict[str, type], optional
|
|
208
|
+
A dict of plugin names to a class that processes that plugin.
|
|
209
|
+
The class will take the Connection object as the argument to the
|
|
210
|
+
constructor. The class needs an authenticate method taking an
|
|
211
|
+
authentication packet as an argument. For the dialog plugin, a
|
|
212
|
+
prompt(echo, prompt) method can be used (if no authenticate method)
|
|
213
|
+
for returning a string from the user. (experimental)
|
|
214
|
+
server_public_key : str, optional
|
|
215
|
+
SHA256 authentication plugin public key value. (default: None)
|
|
216
|
+
binary_prefix : bool, optional
|
|
217
|
+
Add _binary prefix on bytes and bytearray. (default: False)
|
|
218
|
+
compress :
|
|
219
|
+
Not supported.
|
|
220
|
+
named_pipe :
|
|
221
|
+
Not supported.
|
|
222
|
+
db : str, optional
|
|
223
|
+
**DEPRECATED** Alias for database.
|
|
224
|
+
passwd : str, optional
|
|
225
|
+
**DEPRECATED** Alias for password.
|
|
226
|
+
parse_json : bool, optional
|
|
227
|
+
Parse JSON values into Python objects?
|
|
228
|
+
invalid_values : Dict[int, Any], optional
|
|
229
|
+
Dictionary of values to use in place of invalid values
|
|
173
230
|
found during conversion of data. The default is to return the byte content
|
|
174
231
|
containing the invalid value. The keys are the integers associtated with
|
|
175
232
|
the column type.
|
|
176
|
-
|
|
233
|
+
pure_python : bool, optional
|
|
234
|
+
Should we ignore the C extension even if it's available?
|
|
177
235
|
This can be given explicitly using True or False, or if the value is None,
|
|
178
|
-
the C extension will be loaded if it is available.
|
|
236
|
+
the C extension will be loaded if it is available. If set to False and
|
|
237
|
+
the C extension can't be loaded, a NotSupportedError is raised.
|
|
238
|
+
nan_as_null : bool, optional
|
|
239
|
+
Should NaN values be treated as NULLs in parameter substitution including
|
|
240
|
+
uploading data?
|
|
241
|
+
inf_as_null : bool, optional
|
|
242
|
+
Should Inf values be treated as NULLs in parameter substitution including
|
|
243
|
+
uploading data?
|
|
244
|
+
track_env : bool, optional
|
|
245
|
+
Should the connection track the SINGLESTOREDB_URL environment variable?
|
|
246
|
+
|
|
247
|
+
See `Connection <https://www.python.org/dev/peps/pep-0249/#connection-objects>`_
|
|
248
|
+
in the specification.
|
|
179
249
|
|
|
180
|
-
See `Connection <https://www.python.org/dev/peps/pep-0249/#connection-objects>`_ in the
|
|
181
|
-
specification.
|
|
182
250
|
"""
|
|
183
251
|
|
|
252
|
+
driver = 'mysql'
|
|
253
|
+
paramstyle = 'pyformat'
|
|
254
|
+
|
|
184
255
|
_sock = None
|
|
185
256
|
_auth_plugin_name = ''
|
|
186
257
|
_closed = False
|
|
187
258
|
_secure = False
|
|
188
259
|
|
|
189
|
-
def __init__(
|
|
260
|
+
def __init__( # noqa: C901
|
|
190
261
|
self,
|
|
191
262
|
*,
|
|
192
263
|
user=None, # The first four arguments is based on DB-API 2.0 recommendation.
|
|
@@ -196,12 +267,13 @@ class Connection:
|
|
|
196
267
|
unix_socket=None,
|
|
197
268
|
port=0,
|
|
198
269
|
charset='',
|
|
270
|
+
collation=None,
|
|
199
271
|
sql_mode=None,
|
|
200
272
|
read_default_file=None,
|
|
201
273
|
conv=None,
|
|
202
274
|
use_unicode=True,
|
|
203
275
|
client_flag=0,
|
|
204
|
-
cursorclass=
|
|
276
|
+
cursorclass=None,
|
|
205
277
|
init_command=None,
|
|
206
278
|
connect_timeout=10,
|
|
207
279
|
read_default_group=None,
|
|
@@ -219,18 +291,30 @@ class Connection:
|
|
|
219
291
|
ssl=None,
|
|
220
292
|
ssl_ca=None,
|
|
221
293
|
ssl_cert=None,
|
|
294
|
+
ssl_cipher=None,
|
|
222
295
|
ssl_disabled=None,
|
|
223
296
|
ssl_key=None,
|
|
224
297
|
ssl_verify_cert=None,
|
|
225
298
|
ssl_verify_identity=None,
|
|
226
|
-
parse_json=
|
|
299
|
+
parse_json=True,
|
|
227
300
|
invalid_values=None,
|
|
228
301
|
pure_python=None,
|
|
302
|
+
buffered=True,
|
|
303
|
+
results_type='tuples',
|
|
229
304
|
compress=None, # not supported
|
|
230
305
|
named_pipe=None, # not supported
|
|
231
306
|
passwd=None, # deprecated
|
|
232
307
|
db=None, # deprecated
|
|
308
|
+
driver=None, # internal use
|
|
309
|
+
conn_attrs=None,
|
|
310
|
+
multi_statements=None,
|
|
311
|
+
nan_as_null=None,
|
|
312
|
+
inf_as_null=None,
|
|
313
|
+
encoding_errors='strict',
|
|
314
|
+
track_env=False,
|
|
233
315
|
):
|
|
316
|
+
BaseConnection.__init__(**dict(locals()))
|
|
317
|
+
|
|
234
318
|
if db is not None and database is None:
|
|
235
319
|
# We will raise warning in 2022 or later.
|
|
236
320
|
# See https://github.com/PyMySQL/PyMySQL/issues/939
|
|
@@ -252,6 +336,8 @@ class Connection:
|
|
|
252
336
|
self._local_infile = bool(local_infile)
|
|
253
337
|
if self._local_infile:
|
|
254
338
|
client_flag |= CLIENT.LOCAL_FILES
|
|
339
|
+
if multi_statements:
|
|
340
|
+
client_flag |= CLIENT.MULTI_STATEMENTS
|
|
255
341
|
|
|
256
342
|
if read_default_group and not read_default_file:
|
|
257
343
|
if sys.platform.startswith('win'):
|
|
@@ -292,7 +378,8 @@ class Connection:
|
|
|
292
378
|
|
|
293
379
|
self.ssl = False
|
|
294
380
|
if not ssl_disabled:
|
|
295
|
-
if ssl_ca or ssl_cert or ssl_key or
|
|
381
|
+
if ssl_ca or ssl_cert or ssl_key or ssl_cipher or \
|
|
382
|
+
ssl_verify_cert or ssl_verify_identity:
|
|
296
383
|
ssl = {
|
|
297
384
|
'ca': ssl_ca,
|
|
298
385
|
'check_hostname': bool(ssl_verify_identity),
|
|
@@ -304,6 +391,8 @@ class Connection:
|
|
|
304
391
|
ssl['cert'] = ssl_cert
|
|
305
392
|
if ssl_key is not None:
|
|
306
393
|
ssl['key'] = ssl_key
|
|
394
|
+
if ssl_cipher is not None:
|
|
395
|
+
ssl['cipher'] = ssl_cipher
|
|
307
396
|
if ssl:
|
|
308
397
|
if not SSL_ENABLED:
|
|
309
398
|
raise NotImplementedError('ssl module not found')
|
|
@@ -333,31 +422,71 @@ class Connection:
|
|
|
333
422
|
self._write_timeout = write_timeout
|
|
334
423
|
|
|
335
424
|
self.charset = charset or DEFAULT_CHARSET
|
|
425
|
+
self.collation = collation
|
|
336
426
|
self.use_unicode = use_unicode
|
|
427
|
+
self.encoding_errors = encoding_errors
|
|
337
428
|
|
|
338
429
|
self.encoding = charset_by_name(self.charset).encoding
|
|
339
430
|
|
|
340
431
|
client_flag |= CLIENT.CAPABILITIES
|
|
341
|
-
|
|
342
|
-
client_flag |= CLIENT.CONNECT_WITH_DB
|
|
432
|
+
client_flag |= CLIENT.CONNECT_WITH_DB
|
|
343
433
|
|
|
344
434
|
self.client_flag = client_flag
|
|
345
435
|
|
|
346
436
|
self.pure_python = pure_python
|
|
347
|
-
self.
|
|
348
|
-
self.cursorclass = cursorclass
|
|
437
|
+
self.results_type = results_type
|
|
349
438
|
self.resultclass = MySQLResult
|
|
439
|
+
if cursorclass is not None:
|
|
440
|
+
self.cursorclass = cursorclass
|
|
441
|
+
elif buffered:
|
|
442
|
+
if 'dict' in self.results_type:
|
|
443
|
+
self.cursorclass = DictCursor
|
|
444
|
+
elif 'namedtuple' in self.results_type:
|
|
445
|
+
self.cursorclass = NamedtupleCursor
|
|
446
|
+
else:
|
|
447
|
+
self.cursorclass = Cursor
|
|
448
|
+
else:
|
|
449
|
+
if 'dict' in self.results_type:
|
|
450
|
+
self.cursorclass = SSDictCursor
|
|
451
|
+
elif 'namedtuple' in self.results_type:
|
|
452
|
+
self.cursorclass = SSNamedtupleCursor
|
|
453
|
+
else:
|
|
454
|
+
self.cursorclass = SSCursor
|
|
455
|
+
|
|
456
|
+
if self.pure_python is False and _singlestoredb_accel is None:
|
|
457
|
+
try:
|
|
458
|
+
import _singlestortedb_accel # noqa: F401
|
|
459
|
+
except Exception:
|
|
460
|
+
import traceback
|
|
461
|
+
traceback.print_exc(file=sys.stderr)
|
|
462
|
+
finally:
|
|
463
|
+
raise err.NotSupportedError(
|
|
464
|
+
'pure_python=False, but the '
|
|
465
|
+
'C extension can not be loaded',
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if self.pure_python is True:
|
|
469
|
+
pass
|
|
350
470
|
|
|
351
471
|
# The C extension handles these types internally.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
472
|
+
elif _singlestoredb_accel is not None:
|
|
473
|
+
self.resultclass = MySQLResultSV
|
|
474
|
+
if self.cursorclass is Cursor:
|
|
475
|
+
self.cursorclass = CursorSV
|
|
476
|
+
elif self.cursorclass is SSCursor:
|
|
477
|
+
self.cursorclass = SSCursorSV
|
|
478
|
+
elif self.cursorclass is DictCursor:
|
|
479
|
+
self.cursorclass = DictCursorSV
|
|
480
|
+
self.results_type = 'dicts'
|
|
481
|
+
elif self.cursorclass is SSDictCursor:
|
|
482
|
+
self.cursorclass = SSDictCursorSV
|
|
483
|
+
self.results_type = 'dicts'
|
|
484
|
+
elif self.cursorclass is NamedtupleCursor:
|
|
485
|
+
self.cursorclass = NamedtupleCursorSV
|
|
486
|
+
self.results_type = 'namedtuples'
|
|
487
|
+
elif self.cursorclass is SSNamedtupleCursor:
|
|
488
|
+
self.cursorclass = SSNamedtupleCursorSV
|
|
489
|
+
self.results_type = 'namedtuples'
|
|
361
490
|
|
|
362
491
|
self._result = None
|
|
363
492
|
self._affected_rows = 0
|
|
@@ -382,20 +511,46 @@ class Connection:
|
|
|
382
511
|
self._binary_prefix = binary_prefix
|
|
383
512
|
self.server_public_key = server_public_key
|
|
384
513
|
|
|
514
|
+
if self.connection_params['nan_as_null'] or \
|
|
515
|
+
self.connection_params['inf_as_null']:
|
|
516
|
+
float_encoder = self.encoders.get(float)
|
|
517
|
+
if float_encoder is not None:
|
|
518
|
+
self.encoders[float] = functools.partial(
|
|
519
|
+
float_encoder,
|
|
520
|
+
nan_as_null=self.connection_params['nan_as_null'],
|
|
521
|
+
inf_as_null=self.connection_params['inf_as_null'],
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
from .. import __version__ as VERSION_STRING
|
|
525
|
+
|
|
385
526
|
self._connect_attrs = {
|
|
386
|
-
'
|
|
527
|
+
'_os': str(sys.platform),
|
|
387
528
|
'_pid': str(os.getpid()),
|
|
529
|
+
'_client_name': 'SingleStoreDB Python Client',
|
|
388
530
|
'_client_version': VERSION_STRING,
|
|
389
531
|
}
|
|
390
532
|
|
|
391
533
|
if program_name:
|
|
392
534
|
self._connect_attrs['program_name'] = program_name
|
|
535
|
+
if conn_attrs is not None:
|
|
536
|
+
# do not overwrite the attributes that we set ourselves
|
|
537
|
+
for k, v in conn_attrs.items():
|
|
538
|
+
if k not in self._connect_attrs:
|
|
539
|
+
self._connect_attrs[k] = v
|
|
540
|
+
|
|
541
|
+
self._in_sync = False
|
|
542
|
+
self._track_env = bool(track_env) or self.host == 'singlestore.com'
|
|
393
543
|
|
|
394
|
-
if defer_connect:
|
|
544
|
+
if defer_connect or self._track_env:
|
|
395
545
|
self._sock = None
|
|
396
546
|
else:
|
|
397
547
|
self.connect()
|
|
398
548
|
|
|
549
|
+
@property
|
|
550
|
+
def messages(self):
|
|
551
|
+
# TODO
|
|
552
|
+
[]
|
|
553
|
+
|
|
399
554
|
def __enter__(self):
|
|
400
555
|
return self
|
|
401
556
|
|
|
@@ -442,11 +597,17 @@ class Connection:
|
|
|
442
597
|
"""
|
|
443
598
|
Send the quit message and close the socket.
|
|
444
599
|
|
|
445
|
-
See `Connection.close()
|
|
600
|
+
See `Connection.close()
|
|
601
|
+
<https://www.python.org/dev/peps/pep-0249/#Connection.close>`_
|
|
446
602
|
in the specification.
|
|
447
603
|
|
|
448
|
-
|
|
604
|
+
Raises
|
|
605
|
+
------
|
|
606
|
+
Error : If the connection is already closed.
|
|
607
|
+
|
|
449
608
|
"""
|
|
609
|
+
if self.host == 'singlestore.com':
|
|
610
|
+
return
|
|
450
611
|
if self._closed:
|
|
451
612
|
raise err.Error('Already closed')
|
|
452
613
|
self._closed = True
|
|
@@ -465,6 +626,10 @@ class Connection:
|
|
|
465
626
|
"""Return True if the connection is open."""
|
|
466
627
|
return self._sock is not None
|
|
467
628
|
|
|
629
|
+
def is_connected(self):
|
|
630
|
+
"""Return True if the connection is open."""
|
|
631
|
+
return self.open
|
|
632
|
+
|
|
468
633
|
def _force_close(self):
|
|
469
634
|
"""Close connection without QUIT message."""
|
|
470
635
|
if self._sock:
|
|
@@ -478,12 +643,14 @@ class Connection:
|
|
|
478
643
|
__del__ = _force_close
|
|
479
644
|
|
|
480
645
|
def autocommit(self, value):
|
|
646
|
+
"""Enable autocommit in the server."""
|
|
481
647
|
self.autocommit_mode = bool(value)
|
|
482
648
|
current = self.get_autocommit()
|
|
483
649
|
if value != current:
|
|
484
650
|
self._send_autocommit_mode()
|
|
485
651
|
|
|
486
652
|
def get_autocommit(self):
|
|
653
|
+
"""Retrieve autocommit status."""
|
|
487
654
|
return bool(self.server_status & SERVER_STATUS.SERVER_STATUS_AUTOCOMMIT)
|
|
488
655
|
|
|
489
656
|
def _read_ok_packet(self):
|
|
@@ -499,6 +666,7 @@ class Connection:
|
|
|
499
666
|
|
|
500
667
|
def _send_autocommit_mode(self):
|
|
501
668
|
"""Set whether or not to commit after every execute()."""
|
|
669
|
+
log_query('SET AUTOCOMMIT = %s' % self.escape(self.autocommit_mode))
|
|
502
670
|
self._execute_command(
|
|
503
671
|
COMMAND.COM_QUERY, 'SET AUTOCOMMIT = %s' % self.escape(self.autocommit_mode),
|
|
504
672
|
)
|
|
@@ -506,6 +674,9 @@ class Connection:
|
|
|
506
674
|
|
|
507
675
|
def begin(self):
|
|
508
676
|
"""Begin transaction."""
|
|
677
|
+
log_query('BEGIN')
|
|
678
|
+
if self.host == 'singlestore.com':
|
|
679
|
+
return
|
|
509
680
|
self._execute_command(COMMAND.COM_QUERY, 'BEGIN')
|
|
510
681
|
self._read_ok_packet()
|
|
511
682
|
|
|
@@ -515,7 +686,11 @@ class Connection:
|
|
|
515
686
|
|
|
516
687
|
See `Connection.commit() <https://www.python.org/dev/peps/pep-0249/#commit>`_
|
|
517
688
|
in the specification.
|
|
689
|
+
|
|
518
690
|
"""
|
|
691
|
+
log_query('COMMIT')
|
|
692
|
+
if self.host == 'singlestore.com':
|
|
693
|
+
return
|
|
519
694
|
self._execute_command(COMMAND.COM_QUERY, 'COMMIT')
|
|
520
695
|
self._read_ok_packet()
|
|
521
696
|
|
|
@@ -525,12 +700,17 @@ class Connection:
|
|
|
525
700
|
|
|
526
701
|
See `Connection.rollback() <https://www.python.org/dev/peps/pep-0249/#rollback>`_
|
|
527
702
|
in the specification.
|
|
703
|
+
|
|
528
704
|
"""
|
|
705
|
+
log_query('ROLLBACK')
|
|
706
|
+
if self.host == 'singlestore.com':
|
|
707
|
+
return
|
|
529
708
|
self._execute_command(COMMAND.COM_QUERY, 'ROLLBACK')
|
|
530
709
|
self._read_ok_packet()
|
|
531
710
|
|
|
532
711
|
def show_warnings(self):
|
|
533
712
|
"""Send the "SHOW WARNINGS" SQL command."""
|
|
713
|
+
log_query('SHOW WARNINGS')
|
|
534
714
|
self._execute_command(COMMAND.COM_QUERY, 'SHOW WARNINGS')
|
|
535
715
|
result = self.resultclass(self)
|
|
536
716
|
result.read()
|
|
@@ -540,80 +720,102 @@ class Connection:
|
|
|
540
720
|
"""
|
|
541
721
|
Set current db.
|
|
542
722
|
|
|
543
|
-
|
|
723
|
+
db : str
|
|
724
|
+
The name of the db.
|
|
725
|
+
|
|
544
726
|
"""
|
|
545
727
|
self._execute_command(COMMAND.COM_INIT_DB, db)
|
|
546
728
|
self._read_ok_packet()
|
|
547
729
|
|
|
548
730
|
def escape(self, obj, mapping=None):
|
|
549
|
-
"""
|
|
731
|
+
"""
|
|
732
|
+
Escape whatever value is passed.
|
|
550
733
|
|
|
551
734
|
Non-standard, for internal use; do not use this in your applications.
|
|
735
|
+
|
|
552
736
|
"""
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
737
|
+
dtype = type(obj)
|
|
738
|
+
if dtype is str or isinstance(obj, str):
|
|
739
|
+
return "'{}'".format(self.escape_string(obj))
|
|
740
|
+
if dtype is bytes or dtype is bytearray or isinstance(obj, (bytes, bytearray)):
|
|
741
|
+
return self._quote_bytes(obj)
|
|
742
|
+
if mapping is None:
|
|
743
|
+
mapping = self.encoders
|
|
560
744
|
return converters.escape_item(obj, self.charset, mapping=mapping)
|
|
561
745
|
|
|
562
746
|
def literal(self, obj):
|
|
563
|
-
"""
|
|
747
|
+
"""
|
|
748
|
+
Alias for escape().
|
|
564
749
|
|
|
565
750
|
Non-standard, for internal use; do not use this in your applications.
|
|
751
|
+
|
|
566
752
|
"""
|
|
567
753
|
return self.escape(obj, self.encoders)
|
|
568
754
|
|
|
569
755
|
def escape_string(self, s):
|
|
756
|
+
"""Escape a string value."""
|
|
570
757
|
if self.server_status & SERVER_STATUS.SERVER_STATUS_NO_BACKSLASH_ESCAPES:
|
|
571
758
|
return s.replace("'", "''")
|
|
572
759
|
return converters.escape_string(s)
|
|
573
760
|
|
|
574
761
|
def _quote_bytes(self, s):
|
|
575
762
|
if self.server_status & SERVER_STATUS.SERVER_STATUS_NO_BACKSLASH_ESCAPES:
|
|
576
|
-
|
|
763
|
+
if self._binary_prefix:
|
|
764
|
+
return "_binary X'{}'".format(s.hex())
|
|
765
|
+
return "X'{}'".format(s.hex())
|
|
577
766
|
return converters.escape_bytes(s)
|
|
578
767
|
|
|
579
|
-
def cursor(self
|
|
580
|
-
"""
|
|
581
|
-
Create a new cursor to execute queries with.
|
|
582
|
-
|
|
583
|
-
:param cursor: The type of cursor to create. None means use Cursor.
|
|
584
|
-
:type cursor: :py:class:`Cursor`, :py:class:`SSCursor`, :py:class:`DictCursor`, or :py:class:`SSDictCursor`.
|
|
585
|
-
"""
|
|
586
|
-
if cursor:
|
|
587
|
-
# if cursor is SSCursor:
|
|
588
|
-
# cursor = SSCursorSV
|
|
589
|
-
# elif cursor is DictCursor:
|
|
590
|
-
# # TODO: Passing in a cursor shouldn't affect connection output type
|
|
591
|
-
# self.output_type = 'dicts'
|
|
592
|
-
# elif cursor is SSDictCursor:
|
|
593
|
-
# cursor = SSDictCursorSV
|
|
594
|
-
# # TODO: Passing in a cursor shouldn't affect connection output type
|
|
595
|
-
# self.output_type = 'dicts'
|
|
596
|
-
return cursor(self)
|
|
768
|
+
def cursor(self):
|
|
769
|
+
"""Create a new cursor to execute queries with."""
|
|
597
770
|
return self.cursorclass(self)
|
|
598
771
|
|
|
599
772
|
# The following methods are INTERNAL USE ONLY (called from Cursor)
|
|
600
773
|
def query(self, sql, unbuffered=False):
|
|
774
|
+
"""
|
|
775
|
+
Run a query on the server.
|
|
776
|
+
|
|
777
|
+
Internal use only.
|
|
778
|
+
|
|
779
|
+
"""
|
|
601
780
|
# if DEBUG:
|
|
602
781
|
# print("DEBUG: sending query:", sql)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
782
|
+
handler = fusion.get_handler(sql)
|
|
783
|
+
if handler is not None:
|
|
784
|
+
self._result = fusion.execute(self, sql, handler=handler)
|
|
785
|
+
self._affected_rows = self._result.affected_rows
|
|
786
|
+
else:
|
|
787
|
+
if isinstance(sql, str):
|
|
788
|
+
sql = sql.encode(self.encoding, 'surrogateescape')
|
|
789
|
+
self._execute_command(COMMAND.COM_QUERY, sql)
|
|
790
|
+
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
|
|
607
791
|
return self._affected_rows
|
|
608
792
|
|
|
609
793
|
def next_result(self, unbuffered=False):
|
|
794
|
+
"""
|
|
795
|
+
Retrieve the next result set.
|
|
796
|
+
|
|
797
|
+
Internal use only.
|
|
798
|
+
|
|
799
|
+
"""
|
|
610
800
|
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
|
|
611
801
|
return self._affected_rows
|
|
612
802
|
|
|
613
803
|
def affected_rows(self):
|
|
804
|
+
"""
|
|
805
|
+
Return number of affected rows.
|
|
806
|
+
|
|
807
|
+
Internal use only.
|
|
808
|
+
|
|
809
|
+
"""
|
|
614
810
|
return self._affected_rows
|
|
615
811
|
|
|
616
812
|
def kill(self, thread_id):
|
|
813
|
+
"""
|
|
814
|
+
Execute kill command.
|
|
815
|
+
|
|
816
|
+
Internal use only.
|
|
817
|
+
|
|
818
|
+
"""
|
|
617
819
|
arg = struct.pack('<I', thread_id)
|
|
618
820
|
self._execute_command(COMMAND.COM_PROCESS_KILL, arg)
|
|
619
821
|
return self._read_ok_packet()
|
|
@@ -622,10 +824,15 @@ class Connection:
|
|
|
622
824
|
"""
|
|
623
825
|
Check if the server is alive.
|
|
624
826
|
|
|
625
|
-
|
|
626
|
-
|
|
827
|
+
Parameters
|
|
828
|
+
----------
|
|
829
|
+
reconnect : bool, optional
|
|
830
|
+
If the connection is closed, reconnect.
|
|
831
|
+
|
|
832
|
+
Raises
|
|
833
|
+
------
|
|
834
|
+
Error : If the connection is closed and reconnect=False.
|
|
627
835
|
|
|
628
|
-
:raise Error: If the connection is closed and reconnect=False.
|
|
629
836
|
"""
|
|
630
837
|
if self._sock is None:
|
|
631
838
|
if reconnect:
|
|
@@ -644,15 +851,97 @@ class Connection:
|
|
|
644
851
|
raise
|
|
645
852
|
|
|
646
853
|
def set_charset(self, charset):
|
|
854
|
+
"""Deprecated. Use set_character_set() instead."""
|
|
855
|
+
# This function has been implemented in old PyMySQL.
|
|
856
|
+
# But this name is different from MySQLdb.
|
|
857
|
+
# So we keep this function for compatibility and add
|
|
858
|
+
# new set_character_set() function.
|
|
859
|
+
self.set_character_set(charset)
|
|
860
|
+
|
|
861
|
+
def set_character_set(self, charset, collation=None):
|
|
862
|
+
"""
|
|
863
|
+
Set charaset (and collation) on the server.
|
|
864
|
+
|
|
865
|
+
Send "SET NAMES charset [COLLATE collation]" query.
|
|
866
|
+
Update Connection.encoding based on charset.
|
|
867
|
+
|
|
868
|
+
Parameters
|
|
869
|
+
----------
|
|
870
|
+
charset : str
|
|
871
|
+
The charset to enable.
|
|
872
|
+
collation : str, optional
|
|
873
|
+
The collation value
|
|
874
|
+
|
|
875
|
+
"""
|
|
647
876
|
# Make sure charset is supported.
|
|
648
877
|
encoding = charset_by_name(charset).encoding
|
|
649
878
|
|
|
650
|
-
|
|
879
|
+
if collation:
|
|
880
|
+
query = f'SET NAMES {charset} COLLATE {collation}'
|
|
881
|
+
else:
|
|
882
|
+
query = f'SET NAMES {charset}'
|
|
883
|
+
self._execute_command(COMMAND.COM_QUERY, query)
|
|
651
884
|
self._read_packet()
|
|
652
885
|
self.charset = charset
|
|
653
886
|
self.encoding = encoding
|
|
887
|
+
self.collation = collation
|
|
888
|
+
|
|
889
|
+
def _sync_connection(self):
|
|
890
|
+
"""Synchronize connection with env variable."""
|
|
891
|
+
if self._in_sync:
|
|
892
|
+
return
|
|
893
|
+
|
|
894
|
+
if not self._track_env:
|
|
895
|
+
return
|
|
896
|
+
|
|
897
|
+
url = os.environ.get('SINGLESTOREDB_URL')
|
|
898
|
+
if not url:
|
|
899
|
+
return
|
|
900
|
+
|
|
901
|
+
out = {}
|
|
902
|
+
urlp = connection._parse_url(url)
|
|
903
|
+
out.update(urlp)
|
|
904
|
+
|
|
905
|
+
out = connection._cast_params(out)
|
|
906
|
+
|
|
907
|
+
# Set default port based on driver.
|
|
908
|
+
if 'port' not in out or not out['port']:
|
|
909
|
+
out['port'] = int(get_option('port') or 3306)
|
|
910
|
+
|
|
911
|
+
# If there is no user and the password is empty, remove the password key.
|
|
912
|
+
if 'user' not in out and not out.get('password', None):
|
|
913
|
+
out.pop('password', None)
|
|
914
|
+
|
|
915
|
+
if out['host'] == 'singlestore.com':
|
|
916
|
+
raise err.InterfaceError(0, 'Connection URL has not been established')
|
|
917
|
+
|
|
918
|
+
# If it's just a password change, we don't need to reconnect
|
|
919
|
+
if self._sock is not None and \
|
|
920
|
+
(self.host, self.port, self.user, self.db) == \
|
|
921
|
+
(out['host'], out['port'], out['user'], out.get('database')):
|
|
922
|
+
return
|
|
923
|
+
|
|
924
|
+
self.host = out['host']
|
|
925
|
+
self.port = out['port']
|
|
926
|
+
self.user = out['user']
|
|
927
|
+
if isinstance(out['password'], str):
|
|
928
|
+
self.password = out['password'].encode('latin-1')
|
|
929
|
+
else:
|
|
930
|
+
self.password = out['password'] or b''
|
|
931
|
+
self.db = out.get('database')
|
|
932
|
+
try:
|
|
933
|
+
self._in_sync = True
|
|
934
|
+
self.connect()
|
|
935
|
+
finally:
|
|
936
|
+
self._in_sync = False
|
|
654
937
|
|
|
655
938
|
def connect(self, sock=None):
|
|
939
|
+
"""
|
|
940
|
+
Connect to server using existing parameters.
|
|
941
|
+
|
|
942
|
+
Internal use only.
|
|
943
|
+
|
|
944
|
+
"""
|
|
656
945
|
self._closed = False
|
|
657
946
|
try:
|
|
658
947
|
if sock is None:
|
|
@@ -674,7 +963,7 @@ class Connection:
|
|
|
674
963
|
(self.host, self.port), self.connect_timeout, **kwargs,
|
|
675
964
|
)
|
|
676
965
|
break
|
|
677
|
-
except
|
|
966
|
+
except OSError as e:
|
|
678
967
|
if e.errno == errno.EINTR:
|
|
679
968
|
continue
|
|
680
969
|
raise
|
|
@@ -692,18 +981,34 @@ class Connection:
|
|
|
692
981
|
self._get_server_information()
|
|
693
982
|
self._request_authentication()
|
|
694
983
|
|
|
984
|
+
# Send "SET NAMES" query on init for:
|
|
985
|
+
# - Ensure charaset (and collation) is set to the server.
|
|
986
|
+
# - collation_id in handshake packet may be ignored.
|
|
987
|
+
# - If collation is not specified, we don't know what is server's
|
|
988
|
+
# default collation for the charset. For example, default collation
|
|
989
|
+
# of utf8mb4 is:
|
|
990
|
+
# - MySQL 5.7, MariaDB 10.x: utf8mb4_general_ci
|
|
991
|
+
# - MySQL 8.0: utf8mb4_0900_ai_ci
|
|
992
|
+
#
|
|
993
|
+
# Reference:
|
|
994
|
+
# - https://github.com/PyMySQL/PyMySQL/issues/1092
|
|
995
|
+
# - https://github.com/wagtail/wagtail/issues/9477
|
|
996
|
+
# - https://zenn.dev/methane/articles/2023-mysql-collation (Japanese)
|
|
997
|
+
self.set_character_set(self.charset, self.collation)
|
|
998
|
+
|
|
695
999
|
if self.sql_mode is not None:
|
|
696
1000
|
c = self.cursor()
|
|
697
1001
|
c.execute('SET sql_mode=%s', (self.sql_mode,))
|
|
1002
|
+
c.close()
|
|
698
1003
|
|
|
699
1004
|
if self.init_command is not None:
|
|
700
1005
|
c = self.cursor()
|
|
701
1006
|
c.execute(self.init_command)
|
|
702
1007
|
c.close()
|
|
703
|
-
self.commit()
|
|
704
1008
|
|
|
705
1009
|
if self.autocommit_mode is not None:
|
|
706
1010
|
self.autocommit(self.autocommit_mode)
|
|
1011
|
+
|
|
707
1012
|
except BaseException as e:
|
|
708
1013
|
self._rfile = None
|
|
709
1014
|
if sock is not None:
|
|
@@ -712,10 +1017,10 @@ class Connection:
|
|
|
712
1017
|
except: # noqa
|
|
713
1018
|
pass
|
|
714
1019
|
|
|
715
|
-
if isinstance(e, (OSError, IOError)):
|
|
1020
|
+
if isinstance(e, (OSError, IOError, socket.error)):
|
|
716
1021
|
exc = err.OperationalError(
|
|
717
1022
|
CR.CR_CONN_HOST_ERROR,
|
|
718
|
-
|
|
1023
|
+
f'Can\'t connect to MySQL server on {self.host!r} ({e})',
|
|
719
1024
|
)
|
|
720
1025
|
# Keep original exception and traceback to investigate error.
|
|
721
1026
|
exc.original_exception = e
|
|
@@ -730,8 +1035,11 @@ class Connection:
|
|
|
730
1035
|
raise
|
|
731
1036
|
|
|
732
1037
|
def write_packet(self, payload):
|
|
733
|
-
"""
|
|
734
|
-
|
|
1038
|
+
"""
|
|
1039
|
+
Writes an entire "mysql packet" in its entirety to the network.
|
|
1040
|
+
|
|
1041
|
+
Adds its length and sequence number.
|
|
1042
|
+
|
|
735
1043
|
"""
|
|
736
1044
|
# Internal note: when you build packet manually and calls _write_bytes()
|
|
737
1045
|
# directly, you should set self._next_seq_id properly.
|
|
@@ -742,11 +1050,18 @@ class Connection:
|
|
|
742
1050
|
self._next_seq_id = (self._next_seq_id + 1) % 256
|
|
743
1051
|
|
|
744
1052
|
def _read_packet(self, packet_type=MysqlPacket):
|
|
745
|
-
"""
|
|
746
|
-
|
|
1053
|
+
"""
|
|
1054
|
+
Read an entire "mysql packet" in its entirety from the network.
|
|
1055
|
+
|
|
1056
|
+
Raises
|
|
1057
|
+
------
|
|
1058
|
+
OperationalError : If the connection to the MySQL server is lost.
|
|
1059
|
+
InternalError : If the packet sequence number is wrong.
|
|
1060
|
+
|
|
1061
|
+
Returns
|
|
1062
|
+
-------
|
|
1063
|
+
MysqlPacket
|
|
747
1064
|
|
|
748
|
-
:raise OperationalError: If the connection to the MySQL server is lost.
|
|
749
|
-
:raise InternalError: If the packet sequence number is wrong.
|
|
750
1065
|
"""
|
|
751
1066
|
buff = bytearray()
|
|
752
1067
|
while True:
|
|
@@ -787,12 +1102,13 @@ class Connection:
|
|
|
787
1102
|
return packet
|
|
788
1103
|
|
|
789
1104
|
def _read_bytes(self, num_bytes):
|
|
790
|
-
self.
|
|
1105
|
+
if self._read_timeout is not None:
|
|
1106
|
+
self._sock.settimeout(self._read_timeout)
|
|
791
1107
|
while True:
|
|
792
1108
|
try:
|
|
793
1109
|
data = self._rfile.read(num_bytes)
|
|
794
1110
|
break
|
|
795
|
-
except
|
|
1111
|
+
except OSError as e:
|
|
796
1112
|
if e.errno == errno.EINTR:
|
|
797
1113
|
continue
|
|
798
1114
|
self._force_close()
|
|
@@ -812,13 +1128,14 @@ class Connection:
|
|
|
812
1128
|
return data
|
|
813
1129
|
|
|
814
1130
|
def _write_bytes(self, data):
|
|
815
|
-
self.
|
|
1131
|
+
if self._write_timeout is not None:
|
|
1132
|
+
self._sock.settimeout(self._write_timeout)
|
|
816
1133
|
try:
|
|
817
1134
|
self._sock.sendall(data)
|
|
818
|
-
except
|
|
1135
|
+
except OSError as e:
|
|
819
1136
|
self._force_close()
|
|
820
1137
|
raise err.OperationalError(
|
|
821
|
-
CR.CR_SERVER_GONE_ERROR, 'MySQL server has gone away (
|
|
1138
|
+
CR.CR_SERVER_GONE_ERROR, f'MySQL server has gone away ({e!r})',
|
|
822
1139
|
)
|
|
823
1140
|
|
|
824
1141
|
def _read_query_result(self, unbuffered=False):
|
|
@@ -841,11 +1158,18 @@ class Connection:
|
|
|
841
1158
|
|
|
842
1159
|
def _execute_command(self, command, sql):
|
|
843
1160
|
"""
|
|
844
|
-
|
|
845
|
-
|
|
1161
|
+
Execute command.
|
|
1162
|
+
|
|
1163
|
+
Raises
|
|
1164
|
+
------
|
|
1165
|
+
InterfaceError : If the connection is closed.
|
|
1166
|
+
ValueError : If no username was specified.
|
|
1167
|
+
|
|
846
1168
|
"""
|
|
847
|
-
|
|
848
|
-
|
|
1169
|
+
self._sync_connection()
|
|
1170
|
+
|
|
1171
|
+
if self._sock is None:
|
|
1172
|
+
raise err.InterfaceError(0, 'The connection has been closed')
|
|
849
1173
|
|
|
850
1174
|
# If the last query was unbuffered, make sure it finishes before
|
|
851
1175
|
# sending new commands
|
|
@@ -882,7 +1206,7 @@ class Connection:
|
|
|
882
1206
|
if not sql and packet_size < MAX_PACKET_LEN:
|
|
883
1207
|
break
|
|
884
1208
|
|
|
885
|
-
def _request_authentication(self):
|
|
1209
|
+
def _request_authentication(self): # noqa: C901
|
|
886
1210
|
# https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
|
|
887
1211
|
if int(self.server_version.split('.', 1)[0]) >= 5:
|
|
888
1212
|
self.client_flag |= CLIENT.MULTI_RESULTS
|
|
@@ -938,13 +1262,14 @@ class Connection:
|
|
|
938
1262
|
data += _lenenc_int(len(authresp)) + authresp
|
|
939
1263
|
elif self.server_capabilities & CLIENT.SECURE_CONNECTION:
|
|
940
1264
|
data += struct.pack('B', len(authresp)) + authresp
|
|
941
|
-
else: # pragma: no cover -
|
|
1265
|
+
else: # pragma: no cover - no testing against servers w/o secure auth (>=5.0)
|
|
942
1266
|
data += authresp + b'\0'
|
|
943
1267
|
|
|
944
|
-
if self.
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1268
|
+
if self.server_capabilities & CLIENT.CONNECT_WITH_DB:
|
|
1269
|
+
db = self.db
|
|
1270
|
+
if isinstance(db, str):
|
|
1271
|
+
db = db.encode(self.encoding)
|
|
1272
|
+
data += (db or b'') + b'\0'
|
|
948
1273
|
|
|
949
1274
|
if self.server_capabilities & CLIENT.PLUGIN_AUTH:
|
|
950
1275
|
data += (plugin_name or b'') + b'\0'
|
|
@@ -1021,6 +1346,8 @@ class Connection:
|
|
|
1021
1346
|
elif plugin_name == b'mysql_clear_password':
|
|
1022
1347
|
# https://dev.mysql.com/doc/internals/en/clear-text-authentication.html
|
|
1023
1348
|
data = self.password + b'\0'
|
|
1349
|
+
elif plugin_name == b'auth_gssapi_client':
|
|
1350
|
+
data = _auth.gssapi_auth(auth_packet.read_all())
|
|
1024
1351
|
elif plugin_name == b'dialog':
|
|
1025
1352
|
pkt = auth_packet
|
|
1026
1353
|
while True:
|
|
@@ -1157,7 +1484,8 @@ class Connection:
|
|
|
1157
1484
|
if self.server_capabilities & CLIENT.PLUGIN_AUTH and len(data) >= i:
|
|
1158
1485
|
# Due to Bug#59453 the auth-plugin-name is missing the terminating
|
|
1159
1486
|
# NUL-char in versions prior to 5.5.10 and 5.6.2.
|
|
1160
|
-
# ref: https://dev.mysql.com/doc/internals/en/
|
|
1487
|
+
# ref: https://dev.mysql.com/doc/internals/en/
|
|
1488
|
+
# connection-phase-packets.html#packet-Protocol::Handshake
|
|
1161
1489
|
# didn't use version checks as mariadb is corrected and reports
|
|
1162
1490
|
# earlier than those two.
|
|
1163
1491
|
server_end = data.find(b'\0', i)
|
|
@@ -1183,10 +1511,19 @@ class Connection:
|
|
|
1183
1511
|
|
|
1184
1512
|
|
|
1185
1513
|
class MySQLResult:
|
|
1514
|
+
"""
|
|
1515
|
+
Results of a SQL query.
|
|
1516
|
+
|
|
1517
|
+
Parameters
|
|
1518
|
+
----------
|
|
1519
|
+
connection : Connection
|
|
1520
|
+
The connection the result came from.
|
|
1521
|
+
unbuffered : bool, optional
|
|
1522
|
+
Should the reads be unbuffered?
|
|
1523
|
+
|
|
1524
|
+
"""
|
|
1525
|
+
|
|
1186
1526
|
def __init__(self, connection, unbuffered=False):
|
|
1187
|
-
"""
|
|
1188
|
-
:type connection: Connection
|
|
1189
|
-
"""
|
|
1190
1527
|
self.connection = connection
|
|
1191
1528
|
self.affected_rows = None
|
|
1192
1529
|
self.insert_id = None
|
|
@@ -1198,8 +1535,16 @@ class MySQLResult:
|
|
|
1198
1535
|
self.rows = None
|
|
1199
1536
|
self.has_next = None
|
|
1200
1537
|
self.unbuffered_active = False
|
|
1538
|
+
self.converters = []
|
|
1539
|
+
self.fields = []
|
|
1540
|
+
self.encoding_errors = self.connection.encoding_errors
|
|
1201
1541
|
if unbuffered:
|
|
1202
|
-
|
|
1542
|
+
try:
|
|
1543
|
+
self.init_unbuffered_query()
|
|
1544
|
+
except Exception:
|
|
1545
|
+
self.connection = None
|
|
1546
|
+
self.unbuffered_active = False
|
|
1547
|
+
raise
|
|
1203
1548
|
|
|
1204
1549
|
def __del__(self):
|
|
1205
1550
|
if self.unbuffered_active:
|
|
@@ -1220,8 +1565,13 @@ class MySQLResult:
|
|
|
1220
1565
|
|
|
1221
1566
|
def init_unbuffered_query(self):
|
|
1222
1567
|
"""
|
|
1223
|
-
|
|
1224
|
-
|
|
1568
|
+
Initialize an unbuffered query.
|
|
1569
|
+
|
|
1570
|
+
Raises
|
|
1571
|
+
------
|
|
1572
|
+
OperationalError : If the connection to the MySQL server is lost.
|
|
1573
|
+
InternalError : Other errors.
|
|
1574
|
+
|
|
1225
1575
|
"""
|
|
1226
1576
|
self.unbuffered_active = True
|
|
1227
1577
|
first_packet = self.connection._read_packet()
|
|
@@ -1261,7 +1611,7 @@ class MySQLResult:
|
|
|
1261
1611
|
sender = LoadLocalFile(load_packet.filename, self.connection)
|
|
1262
1612
|
try:
|
|
1263
1613
|
sender.send_data()
|
|
1264
|
-
except:
|
|
1614
|
+
except Exception:
|
|
1265
1615
|
self.connection._read_packet() # skip ok packet
|
|
1266
1616
|
raise
|
|
1267
1617
|
|
|
@@ -1281,7 +1631,8 @@ class MySQLResult:
|
|
|
1281
1631
|
# TODO: Support CLIENT.DEPRECATE_EOF
|
|
1282
1632
|
# 1) Add DEPRECATE_EOF to CAPABILITIES
|
|
1283
1633
|
# 2) Mask CAPABILITIES with server_capabilities
|
|
1284
|
-
# 3) if server_capabilities & CLIENT.DEPRECATE_EOF: use OKPacketWrapper
|
|
1634
|
+
# 3) if server_capabilities & CLIENT.DEPRECATE_EOF: use OKPacketWrapper
|
|
1635
|
+
# instead of EOFPacketWrapper
|
|
1285
1636
|
wp = EOFPacketWrapper(packet)
|
|
1286
1637
|
self.warning_count = wp.warning_count
|
|
1287
1638
|
self.has_next = wp.has_next
|
|
@@ -1314,8 +1665,21 @@ class MySQLResult:
|
|
|
1314
1665
|
# After much reading on the MySQL protocol, it appears that there is,
|
|
1315
1666
|
# in fact, no way to stop MySQL from sending all the data after
|
|
1316
1667
|
# executing a query, so we just spin, and wait for an EOF packet.
|
|
1317
|
-
while self.unbuffered_active:
|
|
1318
|
-
|
|
1668
|
+
while self.unbuffered_active and self.connection._sock is not None:
|
|
1669
|
+
try:
|
|
1670
|
+
packet = self.connection._read_packet()
|
|
1671
|
+
except err.OperationalError as e:
|
|
1672
|
+
if e.args[0] in (
|
|
1673
|
+
ER.QUERY_TIMEOUT,
|
|
1674
|
+
ER.STATEMENT_TIMEOUT,
|
|
1675
|
+
):
|
|
1676
|
+
# if the query timed out we can simply ignore this error
|
|
1677
|
+
self.unbuffered_active = False
|
|
1678
|
+
self.connection = None
|
|
1679
|
+
return
|
|
1680
|
+
|
|
1681
|
+
raise
|
|
1682
|
+
|
|
1319
1683
|
if self._check_packet_is_eof(packet):
|
|
1320
1684
|
self.unbuffered_active = False
|
|
1321
1685
|
self.connection = None # release reference to kill cyclic reference.
|
|
@@ -1344,7 +1708,7 @@ class MySQLResult:
|
|
|
1344
1708
|
break
|
|
1345
1709
|
if data is not None:
|
|
1346
1710
|
if encoding is not None:
|
|
1347
|
-
data = data.decode(encoding)
|
|
1711
|
+
data = data.decode(encoding, errors=self.encoding_errors)
|
|
1348
1712
|
if DEBUG:
|
|
1349
1713
|
print('DEBUG: DATA = ', data)
|
|
1350
1714
|
if converter is not None:
|
|
@@ -1397,26 +1761,28 @@ class MySQLResult:
|
|
|
1397
1761
|
|
|
1398
1762
|
|
|
1399
1763
|
class MySQLResultSV(MySQLResult):
|
|
1764
|
+
|
|
1400
1765
|
def __init__(self, connection, unbuffered=False):
|
|
1401
1766
|
MySQLResult.__init__(self, connection, unbuffered=unbuffered)
|
|
1402
1767
|
self.options = {
|
|
1403
1768
|
k: v for k, v in dict(
|
|
1404
1769
|
default_converters=converters.decoders,
|
|
1405
|
-
|
|
1770
|
+
results_type=connection.results_type,
|
|
1406
1771
|
parse_json=connection.parse_json,
|
|
1407
1772
|
invalid_values=connection.invalid_values,
|
|
1408
1773
|
unbuffered=unbuffered,
|
|
1409
1774
|
).items() if v is not UNSET
|
|
1410
1775
|
}
|
|
1411
1776
|
self._read_rowdata_packet = functools.partial(
|
|
1412
|
-
|
|
1777
|
+
_singlestoredb_accel.read_rowdata_packet, self, False,
|
|
1413
1778
|
)
|
|
1414
1779
|
self._read_rowdata_packet_unbuffered = functools.partial(
|
|
1415
|
-
|
|
1780
|
+
_singlestoredb_accel.read_rowdata_packet, self, True,
|
|
1416
1781
|
)
|
|
1417
1782
|
|
|
1418
1783
|
|
|
1419
1784
|
class LoadLocalFile:
|
|
1785
|
+
|
|
1420
1786
|
def __init__(self, filename, connection):
|
|
1421
1787
|
self.filename = filename
|
|
1422
1788
|
self.connection = connection
|
|
@@ -1437,11 +1803,12 @@ class LoadLocalFile:
|
|
|
1437
1803
|
if not chunk:
|
|
1438
1804
|
break
|
|
1439
1805
|
conn.write_packet(chunk)
|
|
1440
|
-
except
|
|
1806
|
+
except OSError:
|
|
1441
1807
|
raise err.OperationalError(
|
|
1442
1808
|
ER.FILE_NOT_FOUND,
|
|
1443
1809
|
f"Can't find file '{self.filename}'",
|
|
1444
1810
|
)
|
|
1445
1811
|
finally:
|
|
1446
|
-
|
|
1447
|
-
|
|
1812
|
+
if not conn._closed:
|
|
1813
|
+
# send the empty packet to signify we are done sending data
|
|
1814
|
+
conn.write_packet(b'')
|