clickhouse-driver 0.2.8__cp312-cp312-musllinux_1_1_s390x.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.
- clickhouse_driver/__init__.py +9 -0
- clickhouse_driver/block.py +227 -0
- clickhouse_driver/blockstreamprofileinfo.py +22 -0
- clickhouse_driver/bufferedreader.cpython-312-s390x-linux-musl.so +0 -0
- clickhouse_driver/bufferedwriter.cpython-312-s390x-linux-musl.so +0 -0
- clickhouse_driver/client.py +896 -0
- clickhouse_driver/clientinfo.py +119 -0
- clickhouse_driver/columns/__init__.py +0 -0
- clickhouse_driver/columns/arraycolumn.py +161 -0
- clickhouse_driver/columns/base.py +221 -0
- clickhouse_driver/columns/boolcolumn.py +7 -0
- clickhouse_driver/columns/datecolumn.py +108 -0
- clickhouse_driver/columns/datetimecolumn.py +202 -0
- clickhouse_driver/columns/decimalcolumn.py +116 -0
- clickhouse_driver/columns/enumcolumn.py +119 -0
- clickhouse_driver/columns/exceptions.py +12 -0
- clickhouse_driver/columns/floatcolumn.py +34 -0
- clickhouse_driver/columns/intcolumn.py +157 -0
- clickhouse_driver/columns/intervalcolumn.py +33 -0
- clickhouse_driver/columns/ipcolumn.py +118 -0
- clickhouse_driver/columns/jsoncolumn.py +37 -0
- clickhouse_driver/columns/largeint.cpython-312-s390x-linux-musl.so +0 -0
- clickhouse_driver/columns/lowcardinalitycolumn.py +142 -0
- clickhouse_driver/columns/mapcolumn.py +73 -0
- clickhouse_driver/columns/nestedcolumn.py +10 -0
- clickhouse_driver/columns/nothingcolumn.py +13 -0
- clickhouse_driver/columns/nullablecolumn.py +7 -0
- clickhouse_driver/columns/nullcolumn.py +15 -0
- clickhouse_driver/columns/numpy/__init__.py +0 -0
- clickhouse_driver/columns/numpy/base.py +47 -0
- clickhouse_driver/columns/numpy/boolcolumn.py +8 -0
- clickhouse_driver/columns/numpy/datecolumn.py +19 -0
- clickhouse_driver/columns/numpy/datetimecolumn.py +143 -0
- clickhouse_driver/columns/numpy/floatcolumn.py +24 -0
- clickhouse_driver/columns/numpy/intcolumn.py +43 -0
- clickhouse_driver/columns/numpy/lowcardinalitycolumn.py +96 -0
- clickhouse_driver/columns/numpy/service.py +58 -0
- clickhouse_driver/columns/numpy/stringcolumn.py +78 -0
- clickhouse_driver/columns/numpy/tuplecolumn.py +37 -0
- clickhouse_driver/columns/service.py +185 -0
- clickhouse_driver/columns/simpleaggregatefunctioncolumn.py +7 -0
- clickhouse_driver/columns/stringcolumn.py +73 -0
- clickhouse_driver/columns/tuplecolumn.py +63 -0
- clickhouse_driver/columns/util.py +60 -0
- clickhouse_driver/columns/uuidcolumn.py +64 -0
- clickhouse_driver/compression/__init__.py +28 -0
- clickhouse_driver/compression/base.py +87 -0
- clickhouse_driver/compression/lz4.py +21 -0
- clickhouse_driver/compression/lz4hc.py +9 -0
- clickhouse_driver/compression/zstd.py +20 -0
- clickhouse_driver/connection.py +784 -0
- clickhouse_driver/context.py +36 -0
- clickhouse_driver/dbapi/__init__.py +62 -0
- clickhouse_driver/dbapi/connection.py +99 -0
- clickhouse_driver/dbapi/cursor.py +370 -0
- clickhouse_driver/dbapi/errors.py +40 -0
- clickhouse_driver/dbapi/extras.py +73 -0
- clickhouse_driver/defines.py +55 -0
- clickhouse_driver/errors.py +453 -0
- clickhouse_driver/log.py +48 -0
- clickhouse_driver/numpy/__init__.py +0 -0
- clickhouse_driver/numpy/block.py +8 -0
- clickhouse_driver/numpy/helpers.py +25 -0
- clickhouse_driver/numpy/result.py +123 -0
- clickhouse_driver/opentelemetry.py +43 -0
- clickhouse_driver/progress.py +38 -0
- clickhouse_driver/protocol.py +114 -0
- clickhouse_driver/queryprocessingstage.py +8 -0
- clickhouse_driver/reader.py +69 -0
- clickhouse_driver/readhelpers.py +26 -0
- clickhouse_driver/result.py +144 -0
- clickhouse_driver/settings/__init__.py +0 -0
- clickhouse_driver/settings/available.py +405 -0
- clickhouse_driver/settings/types.py +50 -0
- clickhouse_driver/settings/writer.py +34 -0
- clickhouse_driver/streams/__init__.py +0 -0
- clickhouse_driver/streams/compressed.py +88 -0
- clickhouse_driver/streams/native.py +102 -0
- clickhouse_driver/util/__init__.py +0 -0
- clickhouse_driver/util/compat.py +39 -0
- clickhouse_driver/util/escape.py +94 -0
- clickhouse_driver/util/helpers.py +57 -0
- clickhouse_driver/varint.cpython-312-s390x-linux-musl.so +0 -0
- clickhouse_driver/writer.py +67 -0
- clickhouse_driver-0.2.8.dist-info/LICENSE +21 -0
- clickhouse_driver-0.2.8.dist-info/METADATA +201 -0
- clickhouse_driver-0.2.8.dist-info/RECORD +89 -0
- clickhouse_driver-0.2.8.dist-info/WHEEL +5 -0
- clickhouse_driver-0.2.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import socket
|
|
3
|
+
import ssl
|
|
4
|
+
from collections import deque
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from sys import platform
|
|
7
|
+
from time import time
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
from . import defines
|
|
11
|
+
from . import errors
|
|
12
|
+
from .block import RowOrientedBlock
|
|
13
|
+
from .blockstreamprofileinfo import BlockStreamProfileInfo
|
|
14
|
+
from .bufferedreader import BufferedSocketReader
|
|
15
|
+
from .bufferedwriter import BufferedSocketWriter
|
|
16
|
+
from .clientinfo import ClientInfo
|
|
17
|
+
from .compression import get_compressor_cls
|
|
18
|
+
from .context import Context
|
|
19
|
+
from .log import log_block
|
|
20
|
+
from .progress import Progress
|
|
21
|
+
from .protocol import Compression, ClientPacketTypes, ServerPacketTypes
|
|
22
|
+
from .queryprocessingstage import QueryProcessingStage
|
|
23
|
+
from .reader import read_binary_str, read_binary_uint64
|
|
24
|
+
from .readhelpers import read_exception
|
|
25
|
+
from .settings.writer import write_settings, SettingsFlags
|
|
26
|
+
from .streams.native import BlockInputStream, BlockOutputStream
|
|
27
|
+
from .util.compat import threading
|
|
28
|
+
from .util.escape import escape_params
|
|
29
|
+
from .varint import write_varint, read_varint
|
|
30
|
+
from .writer import write_binary_str
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Packet(object):
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.type = None
|
|
38
|
+
self.block = None
|
|
39
|
+
self.exception = None
|
|
40
|
+
self.progress = None
|
|
41
|
+
self.profile_info = None
|
|
42
|
+
self.multistring_message = None
|
|
43
|
+
|
|
44
|
+
super(Packet, self).__init__()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ServerInfo(object):
|
|
48
|
+
def __init__(self, name, version_major, version_minor, version_patch,
|
|
49
|
+
revision, timezone, display_name, used_revision):
|
|
50
|
+
self.name = name
|
|
51
|
+
self.version_major = version_major
|
|
52
|
+
self.version_minor = version_minor
|
|
53
|
+
self.version_patch = version_patch
|
|
54
|
+
self.revision = revision
|
|
55
|
+
self.timezone = timezone
|
|
56
|
+
self.display_name = display_name
|
|
57
|
+
self.used_revision = used_revision
|
|
58
|
+
|
|
59
|
+
super(ServerInfo, self).__init__()
|
|
60
|
+
|
|
61
|
+
def version_tuple(self):
|
|
62
|
+
return self.version_major, self.version_minor, self.version_patch
|
|
63
|
+
|
|
64
|
+
def __repr__(self):
|
|
65
|
+
version = '%s.%s.%s' % (
|
|
66
|
+
self.version_major, self.version_minor, self.version_patch
|
|
67
|
+
)
|
|
68
|
+
items = [
|
|
69
|
+
('name', self.name),
|
|
70
|
+
('version', version),
|
|
71
|
+
('revision', self.revision),
|
|
72
|
+
('used revision', self.used_revision),
|
|
73
|
+
('timezone', self.timezone),
|
|
74
|
+
('display_name', self.display_name)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
params = ', '.join('{}={}'.format(key, value) for key, value in items)
|
|
78
|
+
return '<ServerInfo(%s)>' % (params)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Connection(object):
|
|
82
|
+
"""
|
|
83
|
+
Represents connection between client and ClickHouse server.
|
|
84
|
+
|
|
85
|
+
:param host: host with running ClickHouse server.
|
|
86
|
+
:param port: port ClickHouse server is bound to.
|
|
87
|
+
Defaults to ``9000`` if connection is not secured and
|
|
88
|
+
to ``9440`` if connection is secured.
|
|
89
|
+
:param database: database connect to. Defaults to ``'default'``.
|
|
90
|
+
:param user: database user. Defaults to ``'default'``.
|
|
91
|
+
:param password: user's password. Defaults to ``''`` (no password).
|
|
92
|
+
:param client_name: this name will appear in server logs.
|
|
93
|
+
Defaults to ``'python-driver'``.
|
|
94
|
+
:param connect_timeout: timeout for establishing connection.
|
|
95
|
+
Defaults to ``10`` seconds.
|
|
96
|
+
:param send_receive_timeout: timeout for sending and receiving data.
|
|
97
|
+
Defaults to ``300`` seconds.
|
|
98
|
+
:param sync_request_timeout: timeout for server ping.
|
|
99
|
+
Defaults to ``5`` seconds.
|
|
100
|
+
:param compress_block_size: size of compressed block to send.
|
|
101
|
+
Defaults to ``1048576``.
|
|
102
|
+
:param compression: specifies whether or not use compression.
|
|
103
|
+
Defaults to ``False``. Possible choices:
|
|
104
|
+
|
|
105
|
+
* ``True`` is equivalent to ``'lz4'``.
|
|
106
|
+
* ``'lz4'``.
|
|
107
|
+
* ``'lz4hc'`` high-compression variant of
|
|
108
|
+
``'lz4'``.
|
|
109
|
+
* ``'zstd'``.
|
|
110
|
+
|
|
111
|
+
:param secure: establish secure connection. Defaults to ``False``.
|
|
112
|
+
:param verify: specifies whether a certificate is required and whether it
|
|
113
|
+
will be validated after connection.
|
|
114
|
+
Defaults to ``True``.
|
|
115
|
+
:param ssl_version: see :func:`ssl.wrap_socket` docs.
|
|
116
|
+
:param ca_certs: see :func:`ssl.wrap_socket` docs.
|
|
117
|
+
:param ciphers: see :func:`ssl.wrap_socket` docs.
|
|
118
|
+
:param keyfile: see :func:`ssl.wrap_socket` docs.
|
|
119
|
+
:param certfile: see :func:`ssl.wrap_socket` docs.
|
|
120
|
+
:param server_hostname: Hostname to use in SSL Wrapper construction.
|
|
121
|
+
Defaults to `None` which will send the passed
|
|
122
|
+
host param during SSL initialization. This param
|
|
123
|
+
may be used when connecting over an SSH tunnel
|
|
124
|
+
to correctly identify the desired server via SNI.
|
|
125
|
+
:param alt_hosts: list of alternative hosts for connection.
|
|
126
|
+
Example: alt_hosts=host1:port1,host2:port2.
|
|
127
|
+
:param settings_is_important: ``False`` means unknown settings will be
|
|
128
|
+
ignored, ``True`` means that the query will
|
|
129
|
+
fail with UNKNOWN_SETTING error.
|
|
130
|
+
Defaults to ``False``.
|
|
131
|
+
:param tcp_keepalive: enables `TCP keepalive <https://tldp.org/HOWTO/
|
|
132
|
+
TCP-Keepalive-HOWTO/overview.html>`_ on established
|
|
133
|
+
connection. If is set to ``True``` system keepalive
|
|
134
|
+
settings are used. You can also specify custom
|
|
135
|
+
keepalive setting with tuple:
|
|
136
|
+
``(idle_time_sec, interval_sec, probes)``.
|
|
137
|
+
Defaults to ``False``.
|
|
138
|
+
:param client_revision: can be used for client version downgrading.
|
|
139
|
+
Defaults to ``None``.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self, host, port=None,
|
|
144
|
+
database=defines.DEFAULT_DATABASE,
|
|
145
|
+
user=defines.DEFAULT_USER, password=defines.DEFAULT_PASSWORD,
|
|
146
|
+
client_name=defines.CLIENT_NAME,
|
|
147
|
+
connect_timeout=defines.DBMS_DEFAULT_CONNECT_TIMEOUT_SEC,
|
|
148
|
+
send_receive_timeout=defines.DBMS_DEFAULT_TIMEOUT_SEC,
|
|
149
|
+
sync_request_timeout=defines.DBMS_DEFAULT_SYNC_REQUEST_TIMEOUT_SEC,
|
|
150
|
+
compress_block_size=defines.DEFAULT_COMPRESS_BLOCK_SIZE,
|
|
151
|
+
compression=False,
|
|
152
|
+
secure=False,
|
|
153
|
+
# Secure socket parameters.
|
|
154
|
+
verify=True, ssl_version=None, ca_certs=None, ciphers=None,
|
|
155
|
+
keyfile=None, certfile=None,
|
|
156
|
+
server_hostname=None,
|
|
157
|
+
alt_hosts=None,
|
|
158
|
+
settings_is_important=False,
|
|
159
|
+
tcp_keepalive=False,
|
|
160
|
+
client_revision=None
|
|
161
|
+
):
|
|
162
|
+
if secure:
|
|
163
|
+
default_port = defines.DEFAULT_SECURE_PORT
|
|
164
|
+
else:
|
|
165
|
+
default_port = defines.DEFAULT_PORT
|
|
166
|
+
|
|
167
|
+
self.hosts = deque([(host, port or default_port)])
|
|
168
|
+
|
|
169
|
+
if alt_hosts:
|
|
170
|
+
for host in alt_hosts.split(','):
|
|
171
|
+
url = urlparse('clickhouse://' + host)
|
|
172
|
+
self.hosts.append((url.hostname, url.port or default_port))
|
|
173
|
+
|
|
174
|
+
self.database = database
|
|
175
|
+
self.user = user
|
|
176
|
+
self.password = password
|
|
177
|
+
self.client_name = defines.DBMS_NAME + ' ' + client_name
|
|
178
|
+
self.connect_timeout = connect_timeout
|
|
179
|
+
self.send_receive_timeout = send_receive_timeout
|
|
180
|
+
self.sync_request_timeout = sync_request_timeout
|
|
181
|
+
self.settings_is_important = settings_is_important
|
|
182
|
+
self.tcp_keepalive = tcp_keepalive
|
|
183
|
+
self.client_revision = min(
|
|
184
|
+
client_revision or defines.CLIENT_REVISION, defines.CLIENT_REVISION
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self.secure_socket = secure
|
|
188
|
+
self.verify_cert = verify
|
|
189
|
+
|
|
190
|
+
ssl_options = {}
|
|
191
|
+
if ssl_version is not None:
|
|
192
|
+
ssl_options['ssl_version'] = ssl_version
|
|
193
|
+
if ca_certs is not None:
|
|
194
|
+
ssl_options['ca_certs'] = ca_certs
|
|
195
|
+
if ciphers is not None:
|
|
196
|
+
ssl_options['ciphers'] = ciphers
|
|
197
|
+
if keyfile is not None:
|
|
198
|
+
ssl_options['keyfile'] = keyfile
|
|
199
|
+
if certfile is not None:
|
|
200
|
+
ssl_options['certfile'] = certfile
|
|
201
|
+
|
|
202
|
+
self.ssl_options = ssl_options
|
|
203
|
+
|
|
204
|
+
self.server_hostname = server_hostname
|
|
205
|
+
|
|
206
|
+
# Use LZ4 compression by default.
|
|
207
|
+
if compression is True:
|
|
208
|
+
compression = 'lz4'
|
|
209
|
+
|
|
210
|
+
if compression is False:
|
|
211
|
+
self.compression = Compression.DISABLED
|
|
212
|
+
self.compressor_cls = None
|
|
213
|
+
self.compress_block_size = None
|
|
214
|
+
else:
|
|
215
|
+
self.compression = Compression.ENABLED
|
|
216
|
+
self.compressor_cls = get_compressor_cls(compression)
|
|
217
|
+
self.compress_block_size = compress_block_size
|
|
218
|
+
|
|
219
|
+
self.socket = None
|
|
220
|
+
self.fin = None
|
|
221
|
+
self.fout = None
|
|
222
|
+
|
|
223
|
+
self.connected = False
|
|
224
|
+
|
|
225
|
+
self.client_trace_context = None
|
|
226
|
+
self.server_info = None
|
|
227
|
+
self.context = Context()
|
|
228
|
+
|
|
229
|
+
# Block writer/reader
|
|
230
|
+
self.block_in = None
|
|
231
|
+
self.block_out = None
|
|
232
|
+
self.block_in_raw = None # log blocks are always not compressed
|
|
233
|
+
|
|
234
|
+
self._lock = threading.Lock()
|
|
235
|
+
self.is_query_executing = False
|
|
236
|
+
|
|
237
|
+
super(Connection, self).__init__()
|
|
238
|
+
|
|
239
|
+
def __repr__(self):
|
|
240
|
+
dsn = '%s://%s:***@%s:%s/%s' % (
|
|
241
|
+
'clickhouses' if self.secure_socket else 'clickhouse',
|
|
242
|
+
self.user, self.host, self.port, self.database
|
|
243
|
+
) if self.connected else '(not connected)'
|
|
244
|
+
|
|
245
|
+
return '<Connection(dsn=%s, compression=%s)>' % (dsn, self.compression)
|
|
246
|
+
|
|
247
|
+
def get_description(self):
|
|
248
|
+
return '{}:{}'.format(self.host, self.port)
|
|
249
|
+
|
|
250
|
+
def force_connect(self):
|
|
251
|
+
self.check_query_execution()
|
|
252
|
+
|
|
253
|
+
if not self.connected:
|
|
254
|
+
self.connect()
|
|
255
|
+
|
|
256
|
+
elif not self.ping():
|
|
257
|
+
logger.warning('Connection was closed, reconnecting.')
|
|
258
|
+
self.connect()
|
|
259
|
+
|
|
260
|
+
def _create_socket(self, host, port):
|
|
261
|
+
"""
|
|
262
|
+
Acts like socket.create_connection, but wraps socket with SSL
|
|
263
|
+
if connection is secure.
|
|
264
|
+
"""
|
|
265
|
+
ssl_options = {}
|
|
266
|
+
if self.secure_socket:
|
|
267
|
+
if self.verify_cert:
|
|
268
|
+
cert_reqs = ssl.CERT_REQUIRED
|
|
269
|
+
else:
|
|
270
|
+
cert_reqs = ssl.CERT_NONE
|
|
271
|
+
|
|
272
|
+
ssl_options = self.ssl_options.copy()
|
|
273
|
+
ssl_options['cert_reqs'] = cert_reqs
|
|
274
|
+
|
|
275
|
+
err = None
|
|
276
|
+
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
|
277
|
+
af, socktype, proto, canonname, sa = res
|
|
278
|
+
sock = None
|
|
279
|
+
try:
|
|
280
|
+
sock = socket.socket(af, socktype, proto)
|
|
281
|
+
sock.settimeout(self.connect_timeout)
|
|
282
|
+
|
|
283
|
+
if self.secure_socket:
|
|
284
|
+
ssl_context = self._create_ssl_context(ssl_options)
|
|
285
|
+
sock = ssl_context.wrap_socket(
|
|
286
|
+
sock, server_hostname=self.server_hostname or host)
|
|
287
|
+
|
|
288
|
+
sock.connect(sa)
|
|
289
|
+
return sock
|
|
290
|
+
|
|
291
|
+
except socket.error as _:
|
|
292
|
+
err = _
|
|
293
|
+
if sock is not None:
|
|
294
|
+
sock.close()
|
|
295
|
+
|
|
296
|
+
if err is not None:
|
|
297
|
+
raise err
|
|
298
|
+
else:
|
|
299
|
+
raise socket.error("getaddrinfo returns an empty list")
|
|
300
|
+
|
|
301
|
+
def _create_ssl_context(self, ssl_options):
|
|
302
|
+
purpose = ssl.Purpose.SERVER_AUTH
|
|
303
|
+
|
|
304
|
+
version = ssl_options.get('ssl_version', ssl.PROTOCOL_TLS)
|
|
305
|
+
context = ssl.SSLContext(version)
|
|
306
|
+
context.check_hostname = self.verify_cert
|
|
307
|
+
|
|
308
|
+
if 'ca_certs' in ssl_options:
|
|
309
|
+
context.load_verify_locations(ssl_options['ca_certs'])
|
|
310
|
+
elif ssl_options.get('cert_reqs') != ssl.CERT_NONE:
|
|
311
|
+
context.load_default_certs(purpose
|
|
312
|
+
)
|
|
313
|
+
if 'ciphers' in ssl_options:
|
|
314
|
+
context.set_ciphers(ssl_options['ciphers'])
|
|
315
|
+
|
|
316
|
+
if 'cert_reqs' in ssl_options:
|
|
317
|
+
context.verify_mode = ssl_options['cert_reqs']
|
|
318
|
+
|
|
319
|
+
if 'certfile' in ssl_options:
|
|
320
|
+
keyfile = ssl_options.get('keyfile')
|
|
321
|
+
context.load_cert_chain(ssl_options['certfile'], keyfile=keyfile)
|
|
322
|
+
|
|
323
|
+
return context
|
|
324
|
+
|
|
325
|
+
def _init_connection(self, host, port):
|
|
326
|
+
self.socket = self._create_socket(host, port)
|
|
327
|
+
self.connected = True
|
|
328
|
+
self.host, self.port = host, port
|
|
329
|
+
self.socket.settimeout(self.send_receive_timeout)
|
|
330
|
+
|
|
331
|
+
# performance tweak
|
|
332
|
+
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
333
|
+
if self.tcp_keepalive:
|
|
334
|
+
self._set_keepalive()
|
|
335
|
+
|
|
336
|
+
self.fin = BufferedSocketReader(self.socket, defines.BUFFER_SIZE)
|
|
337
|
+
self.fout = BufferedSocketWriter(self.socket, defines.BUFFER_SIZE)
|
|
338
|
+
|
|
339
|
+
self.send_hello()
|
|
340
|
+
self.receive_hello()
|
|
341
|
+
|
|
342
|
+
revision = self.server_info.used_revision
|
|
343
|
+
if revision >= defines.DBMS_MIN_PROTOCOL_VERSION_WITH_ADDENDUM:
|
|
344
|
+
self.send_addendum()
|
|
345
|
+
|
|
346
|
+
self.block_in = self.get_block_in_stream()
|
|
347
|
+
self.block_in_raw = BlockInputStream(self.fin, self.context)
|
|
348
|
+
self.block_out = self.get_block_out_stream()
|
|
349
|
+
|
|
350
|
+
def _set_keepalive(self):
|
|
351
|
+
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
352
|
+
|
|
353
|
+
if not isinstance(self.tcp_keepalive, tuple):
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
idle_time_sec, interval_sec, probes = self.tcp_keepalive
|
|
357
|
+
|
|
358
|
+
if platform == 'linux' or platform == 'win32':
|
|
359
|
+
# This should also work for Windows
|
|
360
|
+
# starting with Windows 10, version 1709.
|
|
361
|
+
self.socket.setsockopt(
|
|
362
|
+
socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, idle_time_sec
|
|
363
|
+
)
|
|
364
|
+
self.socket.setsockopt(
|
|
365
|
+
socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec
|
|
366
|
+
)
|
|
367
|
+
self.socket.setsockopt(
|
|
368
|
+
socket.IPPROTO_TCP, socket.TCP_KEEPCNT, probes
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
elif platform == 'darwin':
|
|
372
|
+
TCP_KEEPALIVE = 0x10
|
|
373
|
+
# Only interval is available in mac os.
|
|
374
|
+
self.socket.setsockopt(
|
|
375
|
+
socket.IPPROTO_TCP, TCP_KEEPALIVE, interval_sec
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def _format_connection_error(self, e, host, port):
|
|
379
|
+
err = (e.strerror + ' ') if e.strerror else ''
|
|
380
|
+
return err + '({}:{})'.format(host, port)
|
|
381
|
+
|
|
382
|
+
def connect(self):
|
|
383
|
+
if self.connected:
|
|
384
|
+
self.disconnect()
|
|
385
|
+
|
|
386
|
+
logger.debug(
|
|
387
|
+
'Connecting. Database: %s. User: %s', self.database, self.user
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
err = None
|
|
391
|
+
for i in range(len(self.hosts)):
|
|
392
|
+
host, port = self.hosts[0]
|
|
393
|
+
logger.debug('Connecting to %s:%s', host, port)
|
|
394
|
+
|
|
395
|
+
try:
|
|
396
|
+
return self._init_connection(host, port)
|
|
397
|
+
|
|
398
|
+
except socket.timeout as e:
|
|
399
|
+
self.disconnect()
|
|
400
|
+
logger.warning(
|
|
401
|
+
'Failed to connect to %s:%s', host, port, exc_info=True
|
|
402
|
+
)
|
|
403
|
+
err_str = self._format_connection_error(e, host, port)
|
|
404
|
+
err = errors.SocketTimeoutError(err_str)
|
|
405
|
+
|
|
406
|
+
except socket.error as e:
|
|
407
|
+
self.disconnect()
|
|
408
|
+
logger.warning(
|
|
409
|
+
'Failed to connect to %s:%s', host, port, exc_info=True
|
|
410
|
+
)
|
|
411
|
+
err_str = self._format_connection_error(e, host, port)
|
|
412
|
+
err = errors.NetworkError(err_str)
|
|
413
|
+
|
|
414
|
+
self.hosts.rotate(-1)
|
|
415
|
+
|
|
416
|
+
if err is not None:
|
|
417
|
+
raise err
|
|
418
|
+
|
|
419
|
+
def reset_state(self):
|
|
420
|
+
self.host = None
|
|
421
|
+
self.port = None
|
|
422
|
+
self.socket = None
|
|
423
|
+
self.fin = None
|
|
424
|
+
self.fout = None
|
|
425
|
+
|
|
426
|
+
self.connected = False
|
|
427
|
+
|
|
428
|
+
self.client_trace_context = None
|
|
429
|
+
self.server_info = None
|
|
430
|
+
|
|
431
|
+
self.block_in = None
|
|
432
|
+
self.block_in_raw = None
|
|
433
|
+
self.block_out = None
|
|
434
|
+
|
|
435
|
+
self.is_query_executing = False
|
|
436
|
+
|
|
437
|
+
def disconnect(self):
|
|
438
|
+
"""
|
|
439
|
+
Closes connection between server and client.
|
|
440
|
+
Frees resources: e.g. closes socket.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
if self.connected:
|
|
444
|
+
# There can be errors on shutdown.
|
|
445
|
+
# We need to close socket and reset state even if it happens.
|
|
446
|
+
try:
|
|
447
|
+
self.socket.shutdown(socket.SHUT_RDWR)
|
|
448
|
+
|
|
449
|
+
except socket.error as e:
|
|
450
|
+
logger.warning('Error on socket shutdown: %s', e)
|
|
451
|
+
|
|
452
|
+
self.socket.close()
|
|
453
|
+
|
|
454
|
+
# Socket can be constructed but not connected.
|
|
455
|
+
elif self.socket:
|
|
456
|
+
self.socket.close()
|
|
457
|
+
|
|
458
|
+
self.reset_state()
|
|
459
|
+
|
|
460
|
+
def send_hello(self):
|
|
461
|
+
write_varint(ClientPacketTypes.HELLO, self.fout)
|
|
462
|
+
write_binary_str(self.client_name, self.fout)
|
|
463
|
+
write_varint(defines.CLIENT_VERSION_MAJOR, self.fout)
|
|
464
|
+
write_varint(defines.CLIENT_VERSION_MINOR, self.fout)
|
|
465
|
+
# NOTE For backward compatibility of the protocol,
|
|
466
|
+
# client cannot send its version_patch.
|
|
467
|
+
write_varint(self.client_revision, self.fout)
|
|
468
|
+
write_binary_str(self.database, self.fout)
|
|
469
|
+
write_binary_str(self.user, self.fout)
|
|
470
|
+
write_binary_str(self.password, self.fout)
|
|
471
|
+
|
|
472
|
+
self.fout.flush()
|
|
473
|
+
|
|
474
|
+
def receive_hello(self):
|
|
475
|
+
packet_type = read_varint(self.fin)
|
|
476
|
+
|
|
477
|
+
if packet_type == ServerPacketTypes.HELLO:
|
|
478
|
+
server_name = read_binary_str(self.fin)
|
|
479
|
+
server_version_major = read_varint(self.fin)
|
|
480
|
+
server_version_minor = read_varint(self.fin)
|
|
481
|
+
server_revision = read_varint(self.fin)
|
|
482
|
+
|
|
483
|
+
used_revision = min(self.client_revision, server_revision)
|
|
484
|
+
|
|
485
|
+
server_timezone = None
|
|
486
|
+
if used_revision >= \
|
|
487
|
+
defines.DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE:
|
|
488
|
+
server_timezone = read_binary_str(self.fin)
|
|
489
|
+
|
|
490
|
+
server_display_name = ''
|
|
491
|
+
if used_revision >= \
|
|
492
|
+
defines.DBMS_MIN_REVISION_WITH_SERVER_DISPLAY_NAME:
|
|
493
|
+
server_display_name = read_binary_str(self.fin)
|
|
494
|
+
|
|
495
|
+
server_version_patch = server_revision
|
|
496
|
+
if used_revision >= \
|
|
497
|
+
defines.DBMS_MIN_REVISION_WITH_VERSION_PATCH:
|
|
498
|
+
server_version_patch = read_varint(self.fin)
|
|
499
|
+
|
|
500
|
+
if used_revision >= defines. \
|
|
501
|
+
DBMS_MIN_PROTOCOL_VERSION_WITH_PASSWORD_COMPLEXITY_RULES:
|
|
502
|
+
rules_size = read_varint(self.fin)
|
|
503
|
+
for _i in range(rules_size):
|
|
504
|
+
read_binary_str(self.fin) # original_pattern
|
|
505
|
+
read_binary_str(self.fin) # exception_message
|
|
506
|
+
|
|
507
|
+
if used_revision >= defines. \
|
|
508
|
+
DBMS_MIN_REVISION_WITH_INTERSERVER_SECRET_V2:
|
|
509
|
+
read_binary_uint64(self.fin) # read_nonce
|
|
510
|
+
|
|
511
|
+
self.server_info = ServerInfo(
|
|
512
|
+
server_name, server_version_major, server_version_minor,
|
|
513
|
+
server_version_patch, server_revision,
|
|
514
|
+
server_timezone, server_display_name, used_revision
|
|
515
|
+
)
|
|
516
|
+
self.context.server_info = self.server_info
|
|
517
|
+
|
|
518
|
+
logger.debug(
|
|
519
|
+
'Connected to %s server version %s.%s.%s, revision: %s',
|
|
520
|
+
server_name, server_version_major, server_version_minor,
|
|
521
|
+
server_version_patch, server_revision
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
elif packet_type == ServerPacketTypes.EXCEPTION:
|
|
525
|
+
raise self.receive_exception()
|
|
526
|
+
|
|
527
|
+
else:
|
|
528
|
+
message = self.unexpected_packet_message('Hello or Exception',
|
|
529
|
+
packet_type)
|
|
530
|
+
self.disconnect()
|
|
531
|
+
raise errors.UnexpectedPacketFromServerError(message)
|
|
532
|
+
|
|
533
|
+
def send_addendum(self):
|
|
534
|
+
revision = self.server_info.used_revision
|
|
535
|
+
|
|
536
|
+
if revision >= defines.DBMS_MIN_PROTOCOL_VERSION_WITH_QUOTA_KEY:
|
|
537
|
+
write_binary_str(
|
|
538
|
+
self.context.client_settings['quota_key'], self.fout
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
def ping(self):
|
|
542
|
+
timeout = self.sync_request_timeout
|
|
543
|
+
|
|
544
|
+
with self.timeout_setter(timeout):
|
|
545
|
+
try:
|
|
546
|
+
write_varint(ClientPacketTypes.PING, self.fout)
|
|
547
|
+
self.fout.flush()
|
|
548
|
+
|
|
549
|
+
packet_type = read_varint(self.fin)
|
|
550
|
+
while packet_type == ServerPacketTypes.PROGRESS:
|
|
551
|
+
self.receive_progress()
|
|
552
|
+
packet_type = read_varint(self.fin)
|
|
553
|
+
|
|
554
|
+
if packet_type != ServerPacketTypes.PONG:
|
|
555
|
+
msg = self.unexpected_packet_message('Pong', packet_type)
|
|
556
|
+
raise errors.UnexpectedPacketFromServerError(msg)
|
|
557
|
+
|
|
558
|
+
except errors.Error:
|
|
559
|
+
raise
|
|
560
|
+
|
|
561
|
+
except (socket.error, EOFError) as e:
|
|
562
|
+
# It's just a warning now.
|
|
563
|
+
# Current connection will be closed, new will be established.
|
|
564
|
+
logger.warning(
|
|
565
|
+
'Error on %s ping: %s', self.get_description(), e
|
|
566
|
+
)
|
|
567
|
+
return False
|
|
568
|
+
|
|
569
|
+
return True
|
|
570
|
+
|
|
571
|
+
def receive_packet(self):
|
|
572
|
+
packet = Packet()
|
|
573
|
+
|
|
574
|
+
packet.type = packet_type = read_varint(self.fin)
|
|
575
|
+
|
|
576
|
+
if packet_type == ServerPacketTypes.DATA:
|
|
577
|
+
packet.block = self.receive_data(may_be_use_numpy=True)
|
|
578
|
+
|
|
579
|
+
elif packet_type == ServerPacketTypes.EXCEPTION:
|
|
580
|
+
packet.exception = self.receive_exception()
|
|
581
|
+
|
|
582
|
+
elif packet_type == ServerPacketTypes.PROGRESS:
|
|
583
|
+
packet.progress = self.receive_progress()
|
|
584
|
+
|
|
585
|
+
elif packet_type == ServerPacketTypes.PROFILE_INFO:
|
|
586
|
+
packet.profile_info = self.receive_profile_info()
|
|
587
|
+
|
|
588
|
+
elif packet_type == ServerPacketTypes.TOTALS:
|
|
589
|
+
packet.block = self.receive_data()
|
|
590
|
+
|
|
591
|
+
elif packet_type == ServerPacketTypes.EXTREMES:
|
|
592
|
+
packet.block = self.receive_data()
|
|
593
|
+
|
|
594
|
+
elif packet_type == ServerPacketTypes.LOG:
|
|
595
|
+
packet.block = self.receive_data(may_be_compressed=False)
|
|
596
|
+
log_block(packet.block)
|
|
597
|
+
|
|
598
|
+
elif packet_type == ServerPacketTypes.END_OF_STREAM:
|
|
599
|
+
self.is_query_executing = False
|
|
600
|
+
pass
|
|
601
|
+
|
|
602
|
+
elif packet_type == ServerPacketTypes.TABLE_COLUMNS:
|
|
603
|
+
packet.multistring_message = self.receive_multistring_message(
|
|
604
|
+
packet_type
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
elif packet_type == ServerPacketTypes.PART_UUIDS:
|
|
608
|
+
packet.block = self.receive_data()
|
|
609
|
+
|
|
610
|
+
elif packet_type == ServerPacketTypes.READ_TASK_REQUEST:
|
|
611
|
+
packet.block = self.receive_data()
|
|
612
|
+
|
|
613
|
+
elif packet_type == ServerPacketTypes.PROFILE_EVENTS:
|
|
614
|
+
packet.block = self.receive_data(may_be_compressed=False)
|
|
615
|
+
|
|
616
|
+
else:
|
|
617
|
+
message = 'Unknown packet {} from server {}'.format(
|
|
618
|
+
packet_type, self.get_description()
|
|
619
|
+
)
|
|
620
|
+
self.disconnect()
|
|
621
|
+
raise errors.UnknownPacketFromServerError(message)
|
|
622
|
+
|
|
623
|
+
return packet
|
|
624
|
+
|
|
625
|
+
def get_block_in_stream(self):
|
|
626
|
+
if self.compression:
|
|
627
|
+
from .streams.compressed import CompressedBlockInputStream
|
|
628
|
+
|
|
629
|
+
return CompressedBlockInputStream(self.fin, self.context)
|
|
630
|
+
else:
|
|
631
|
+
return BlockInputStream(self.fin, self.context)
|
|
632
|
+
|
|
633
|
+
def get_block_out_stream(self):
|
|
634
|
+
if self.compression:
|
|
635
|
+
from .streams.compressed import CompressedBlockOutputStream
|
|
636
|
+
|
|
637
|
+
return CompressedBlockOutputStream(
|
|
638
|
+
self.compressor_cls, self.compress_block_size,
|
|
639
|
+
self.fout, self.context
|
|
640
|
+
)
|
|
641
|
+
else:
|
|
642
|
+
return BlockOutputStream(self.fout, self.context)
|
|
643
|
+
|
|
644
|
+
def receive_data(self, may_be_compressed=True, may_be_use_numpy=False):
|
|
645
|
+
revision = self.server_info.used_revision
|
|
646
|
+
|
|
647
|
+
if revision >= defines.DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES:
|
|
648
|
+
read_binary_str(self.fin)
|
|
649
|
+
|
|
650
|
+
reader = self.block_in if may_be_compressed else self.block_in_raw
|
|
651
|
+
use_numpy = False if not may_be_use_numpy else None
|
|
652
|
+
return reader.read(use_numpy=use_numpy)
|
|
653
|
+
|
|
654
|
+
def receive_exception(self):
|
|
655
|
+
return read_exception(self.fin)
|
|
656
|
+
|
|
657
|
+
def receive_progress(self):
|
|
658
|
+
progress = Progress()
|
|
659
|
+
progress.read(self.server_info, self.fin)
|
|
660
|
+
return progress
|
|
661
|
+
|
|
662
|
+
def receive_profile_info(self):
|
|
663
|
+
profile_info = BlockStreamProfileInfo()
|
|
664
|
+
profile_info.read(self.fin)
|
|
665
|
+
return profile_info
|
|
666
|
+
|
|
667
|
+
def receive_multistring_message(self, packet_type):
|
|
668
|
+
num = ServerPacketTypes.strings_in_message(packet_type)
|
|
669
|
+
return [read_binary_str(self.fin) for _i in range(num)]
|
|
670
|
+
|
|
671
|
+
def send_data(self, block, table_name=''):
|
|
672
|
+
start = time()
|
|
673
|
+
write_varint(ClientPacketTypes.DATA, self.fout)
|
|
674
|
+
|
|
675
|
+
revision = self.server_info.used_revision
|
|
676
|
+
if revision >= defines.DBMS_MIN_REVISION_WITH_TEMPORARY_TABLES:
|
|
677
|
+
write_binary_str(table_name, self.fout)
|
|
678
|
+
|
|
679
|
+
self.block_out.write(block)
|
|
680
|
+
logger.debug('Block "%s" send time: %f', table_name, time() - start)
|
|
681
|
+
|
|
682
|
+
def send_query(self, query, query_id=None, params=None):
|
|
683
|
+
if not self.connected:
|
|
684
|
+
self.connect()
|
|
685
|
+
|
|
686
|
+
write_varint(ClientPacketTypes.QUERY, self.fout)
|
|
687
|
+
|
|
688
|
+
write_binary_str(query_id or '', self.fout)
|
|
689
|
+
|
|
690
|
+
revision = self.server_info.used_revision
|
|
691
|
+
if revision >= defines.DBMS_MIN_REVISION_WITH_CLIENT_INFO:
|
|
692
|
+
client_info = ClientInfo(self.client_name, self.context,
|
|
693
|
+
client_revision=self.client_revision)
|
|
694
|
+
client_info.query_kind = ClientInfo.QueryKind.INITIAL_QUERY
|
|
695
|
+
|
|
696
|
+
client_info.write(revision, self.fout)
|
|
697
|
+
|
|
698
|
+
settings_as_strings = (
|
|
699
|
+
revision >= defines
|
|
700
|
+
.DBMS_MIN_REVISION_WITH_SETTINGS_SERIALIZED_AS_STRINGS
|
|
701
|
+
)
|
|
702
|
+
settings_flags = 0
|
|
703
|
+
if self.settings_is_important:
|
|
704
|
+
settings_flags |= SettingsFlags.IMPORTANT
|
|
705
|
+
write_settings(self.context.settings, self.fout, settings_as_strings,
|
|
706
|
+
settings_flags)
|
|
707
|
+
|
|
708
|
+
if revision >= defines.DBMS_MIN_REVISION_WITH_INTERSERVER_SECRET:
|
|
709
|
+
write_binary_str('', self.fout)
|
|
710
|
+
|
|
711
|
+
write_varint(QueryProcessingStage.COMPLETE, self.fout)
|
|
712
|
+
write_varint(self.compression, self.fout)
|
|
713
|
+
|
|
714
|
+
write_binary_str(query, self.fout)
|
|
715
|
+
|
|
716
|
+
if revision >= defines.DBMS_MIN_PROTOCOL_VERSION_WITH_PARAMETERS:
|
|
717
|
+
if self.context.client_settings['server_side_params']:
|
|
718
|
+
# Always settings_as_strings = True
|
|
719
|
+
escaped = escape_params(
|
|
720
|
+
params or {}, self.context, for_server=True
|
|
721
|
+
)
|
|
722
|
+
else:
|
|
723
|
+
escaped = {}
|
|
724
|
+
write_settings(escaped, self.fout, True, SettingsFlags.CUSTOM)
|
|
725
|
+
|
|
726
|
+
logger.debug('Query: %s', query)
|
|
727
|
+
|
|
728
|
+
self.fout.flush()
|
|
729
|
+
|
|
730
|
+
def send_cancel(self):
|
|
731
|
+
write_varint(ClientPacketTypes.CANCEL, self.fout)
|
|
732
|
+
|
|
733
|
+
self.fout.flush()
|
|
734
|
+
|
|
735
|
+
def send_external_tables(self, tables, types_check=False):
|
|
736
|
+
for table in tables or []:
|
|
737
|
+
if not table['structure']:
|
|
738
|
+
raise ValueError(
|
|
739
|
+
'Empty table "{}" structure'.format(table['name'])
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
data = table['data']
|
|
743
|
+
block_cls = RowOrientedBlock
|
|
744
|
+
|
|
745
|
+
if self.context.client_settings['use_numpy']:
|
|
746
|
+
from .numpy.block import NumpyColumnOrientedBlock
|
|
747
|
+
|
|
748
|
+
columns = [x[0] for x in table['structure']]
|
|
749
|
+
data = [data[column].values for column in columns]
|
|
750
|
+
|
|
751
|
+
block_cls = NumpyColumnOrientedBlock
|
|
752
|
+
|
|
753
|
+
block = block_cls(table['structure'], data,
|
|
754
|
+
types_check=types_check)
|
|
755
|
+
self.send_data(block, table_name=table['name'])
|
|
756
|
+
|
|
757
|
+
# Empty block, end of data transfer.
|
|
758
|
+
self.send_data(RowOrientedBlock())
|
|
759
|
+
|
|
760
|
+
@contextmanager
|
|
761
|
+
def timeout_setter(self, new_timeout):
|
|
762
|
+
old_timeout = self.socket.gettimeout()
|
|
763
|
+
self.socket.settimeout(new_timeout)
|
|
764
|
+
|
|
765
|
+
yield
|
|
766
|
+
|
|
767
|
+
self.socket.settimeout(old_timeout)
|
|
768
|
+
|
|
769
|
+
def unexpected_packet_message(self, expected, packet_type):
|
|
770
|
+
packet_type = ServerPacketTypes.to_str(packet_type)
|
|
771
|
+
|
|
772
|
+
return (
|
|
773
|
+
'Unexpected packet from server {} (expected {}, got {})'
|
|
774
|
+
.format(self.get_description(), expected, packet_type)
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
def check_query_execution(self):
|
|
778
|
+
self._lock.acquire(blocking=False)
|
|
779
|
+
|
|
780
|
+
if self.is_query_executing:
|
|
781
|
+
raise errors.PartiallyConsumedQueryError()
|
|
782
|
+
|
|
783
|
+
self.is_query_executing = True
|
|
784
|
+
self._lock.release()
|