clickhouse-driver 0.2.2__cp38-cp38-macosx_10_9_x86_64.whl → 0.2.9__cp38-cp38-macosx_10_9_x86_64.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 +1 -1
- clickhouse_driver/block.py +32 -3
- clickhouse_driver/bufferedreader.cpython-38-darwin.so +0 -0
- clickhouse_driver/bufferedwriter.cpython-38-darwin.so +0 -0
- clickhouse_driver/client.py +243 -123
- clickhouse_driver/clientinfo.py +24 -4
- clickhouse_driver/columns/arraycolumn.py +19 -8
- clickhouse_driver/columns/base.py +81 -7
- clickhouse_driver/columns/boolcolumn.py +7 -0
- clickhouse_driver/columns/datecolumn.py +52 -13
- clickhouse_driver/columns/datetimecolumn.py +5 -11
- clickhouse_driver/columns/decimalcolumn.py +4 -6
- clickhouse_driver/columns/enumcolumn.py +27 -17
- clickhouse_driver/columns/jsoncolumn.py +37 -0
- clickhouse_driver/columns/largeint.cpython-38-darwin.so +0 -0
- clickhouse_driver/columns/lowcardinalitycolumn.py +25 -6
- clickhouse_driver/columns/mapcolumn.py +19 -4
- clickhouse_driver/columns/nestedcolumn.py +10 -0
- clickhouse_driver/columns/numpy/boolcolumn.py +8 -0
- clickhouse_driver/columns/numpy/datetimecolumn.py +24 -27
- clickhouse_driver/columns/numpy/lowcardinalitycolumn.py +5 -4
- clickhouse_driver/columns/numpy/service.py +19 -41
- clickhouse_driver/columns/numpy/tuplecolumn.py +37 -0
- clickhouse_driver/columns/service.py +71 -17
- clickhouse_driver/columns/tuplecolumn.py +32 -34
- clickhouse_driver/columns/util.py +61 -0
- clickhouse_driver/columns/uuidcolumn.py +1 -1
- clickhouse_driver/compression/base.py +39 -4
- clickhouse_driver/compression/lz4.py +4 -38
- clickhouse_driver/compression/zstd.py +4 -35
- clickhouse_driver/connection.py +182 -27
- clickhouse_driver/dbapi/connection.py +6 -3
- clickhouse_driver/dbapi/cursor.py +3 -1
- clickhouse_driver/dbapi/extras.py +73 -0
- clickhouse_driver/defines.py +17 -2
- clickhouse_driver/errors.py +7 -0
- clickhouse_driver/log.py +7 -3
- clickhouse_driver/numpy/helpers.py +5 -2
- clickhouse_driver/progress.py +15 -3
- clickhouse_driver/protocol.py +28 -3
- clickhouse_driver/settings/writer.py +7 -2
- clickhouse_driver/streams/native.py +25 -7
- clickhouse_driver/util/compat.py +39 -0
- clickhouse_driver/util/escape.py +47 -8
- clickhouse_driver/util/helpers.py +114 -0
- clickhouse_driver/varint.cpython-38-darwin.so +0 -0
- {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/METADATA +15 -15
- clickhouse_driver-0.2.9.dist-info/RECORD +89 -0
- {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/WHEEL +1 -1
- clickhouse_driver-0.2.2.dist-info/RECORD +0 -81
- {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/LICENSE +0 -0
- {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/top_level.txt +0 -0
clickhouse_driver/__init__.py
CHANGED
clickhouse_driver/block.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from .columns.util import get_inner_spec, get_inner_columns_with_types
|
|
1
2
|
from .reader import read_varint, read_binary_uint8, read_binary_int32
|
|
2
3
|
from .varint import write_varint
|
|
3
4
|
from .writer import write_binary_uint8, write_binary_int32
|
|
@@ -151,17 +152,45 @@ class RowOrientedBlock(BaseBlock):
|
|
|
151
152
|
return [row[index] for row in self.data]
|
|
152
153
|
|
|
153
154
|
def _mutate_dicts_to_rows(self, data):
|
|
154
|
-
column_names = [x[0] for x in self.columns_with_types]
|
|
155
|
-
|
|
156
155
|
check_row_type = False
|
|
157
156
|
if self.types_check:
|
|
158
157
|
check_row_type = self._check_dict_row_type
|
|
159
158
|
|
|
159
|
+
return self._pure_mutate_dicts_to_rows(
|
|
160
|
+
data,
|
|
161
|
+
self.columns_with_types,
|
|
162
|
+
check_row_type,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _pure_mutate_dicts_to_rows(
|
|
166
|
+
self,
|
|
167
|
+
data,
|
|
168
|
+
columns_with_types,
|
|
169
|
+
check_row_type,
|
|
170
|
+
):
|
|
171
|
+
columns_with_cwt = []
|
|
172
|
+
for name, type_ in columns_with_types:
|
|
173
|
+
cwt = None
|
|
174
|
+
if type_.startswith('Nested'):
|
|
175
|
+
inner_spec = get_inner_spec('Nested', type_)
|
|
176
|
+
cwt = get_inner_columns_with_types(inner_spec)
|
|
177
|
+
columns_with_cwt.append((name, cwt))
|
|
178
|
+
|
|
160
179
|
for i, row in enumerate(data):
|
|
161
180
|
if check_row_type:
|
|
162
181
|
check_row_type(row)
|
|
163
182
|
|
|
164
|
-
|
|
183
|
+
new_data = []
|
|
184
|
+
for name, cwt in columns_with_cwt:
|
|
185
|
+
if cwt is None:
|
|
186
|
+
new_data.append(row[name])
|
|
187
|
+
else:
|
|
188
|
+
new_data.append(self._pure_mutate_dicts_to_rows(
|
|
189
|
+
row[name], cwt, check_row_type
|
|
190
|
+
))
|
|
191
|
+
data[i] = new_data
|
|
192
|
+
# return for recursion
|
|
193
|
+
return data
|
|
165
194
|
|
|
166
195
|
def _check_rows(self, data):
|
|
167
196
|
expected_row_len = len(self.columns_with_types)
|
|
Binary file
|
|
Binary file
|
clickhouse_driver/client.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import re
|
|
2
|
-
import
|
|
2
|
+
from collections import deque
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
from time import time
|
|
5
5
|
import types
|
|
6
|
-
from urllib.parse import urlparse
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
7
|
|
|
8
8
|
from . import errors, defines
|
|
9
9
|
from .block import ColumnOrientedBlock, RowOrientedBlock
|
|
@@ -14,7 +14,7 @@ from .result import (
|
|
|
14
14
|
IterQueryResult, ProgressQueryResult, QueryResult, QueryInfo
|
|
15
15
|
)
|
|
16
16
|
from .util.escape import escape_params
|
|
17
|
-
from .util.helpers import column_chunks, chunks,
|
|
17
|
+
from .util.helpers import column_chunks, chunks, parse_url
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class Client(object):
|
|
@@ -26,7 +26,7 @@ class Client(object):
|
|
|
26
26
|
for the client settings, see below). Defaults to ``None``
|
|
27
27
|
(no additional settings). See all available settings in
|
|
28
28
|
`ClickHouse docs
|
|
29
|
-
<https://clickhouse.
|
|
29
|
+
<https://clickhouse.com/docs/en/operations/settings/settings/>`_.
|
|
30
30
|
:param \\**kwargs: All other args are passed to the
|
|
31
31
|
:py:class:`~clickhouse_driver.connection.Connection`
|
|
32
32
|
constructor.
|
|
@@ -38,7 +38,7 @@ class Client(object):
|
|
|
38
38
|
Defaults to ``1048576``.
|
|
39
39
|
* ``strings_as_bytes`` -- turns off string column encoding/decoding.
|
|
40
40
|
* ``strings_encoding`` -- specifies string encoding. UTF-8 by default.
|
|
41
|
-
* ``use_numpy`` -- Use
|
|
41
|
+
* ``use_numpy`` -- Use NumPy for columns reading. New in version
|
|
42
42
|
*0.2.0*.
|
|
43
43
|
* ``opentelemetry_traceparent`` -- OpenTelemetry traceparent header as
|
|
44
44
|
described by W3C Trace Context recommendation.
|
|
@@ -46,6 +46,27 @@ class Client(object):
|
|
|
46
46
|
* ``opentelemetry_tracestate`` -- OpenTelemetry tracestate header as
|
|
47
47
|
described by W3C Trace Context recommendation.
|
|
48
48
|
New in version *0.2.2*.
|
|
49
|
+
* ``quota_key`` -- A string to differentiate quotas when the user have
|
|
50
|
+
keyed quotas configured on server.
|
|
51
|
+
New in version *0.2.3*.
|
|
52
|
+
* ``input_format_null_as_default`` -- Initialize null fields with
|
|
53
|
+
default values if data type of this field is not
|
|
54
|
+
nullable. Does not work for NumPy. Default: False.
|
|
55
|
+
New in version *0.2.4*.
|
|
56
|
+
* ``round_robin`` -- If ``alt_hosts`` are provided the query will be
|
|
57
|
+
executed on host picked with round-robin algorithm.
|
|
58
|
+
New in version *0.2.5*.
|
|
59
|
+
* ``namedtuple_as_json`` -- Controls named tuple and nested types
|
|
60
|
+
deserialization. To interpret these column alongside
|
|
61
|
+
with ``allow_experimental_object_type=1`` as Python
|
|
62
|
+
tuple set ``namedtuple_as_json`` to ``False``.
|
|
63
|
+
Default: True.
|
|
64
|
+
New in version *0.2.6*.
|
|
65
|
+
* ``server_side_params`` -- Species on which side query parameters
|
|
66
|
+
should be rendered into placeholders.
|
|
67
|
+
Default: False. Means that parameters are rendered
|
|
68
|
+
on driver's side.
|
|
69
|
+
New in version *0.2.7*.
|
|
49
70
|
"""
|
|
50
71
|
|
|
51
72
|
available_client_settings = (
|
|
@@ -54,11 +75,15 @@ class Client(object):
|
|
|
54
75
|
'strings_encoding',
|
|
55
76
|
'use_numpy',
|
|
56
77
|
'opentelemetry_traceparent',
|
|
57
|
-
'opentelemetry_tracestate'
|
|
78
|
+
'opentelemetry_tracestate',
|
|
79
|
+
'quota_key',
|
|
80
|
+
'input_format_null_as_default',
|
|
81
|
+
'namedtuple_as_json',
|
|
82
|
+
'server_side_params'
|
|
58
83
|
)
|
|
59
84
|
|
|
60
85
|
def __init__(self, *args, **kwargs):
|
|
61
|
-
self.settings = kwargs.pop('settings', {}).copy()
|
|
86
|
+
self.settings = (kwargs.pop('settings', None) or {}).copy()
|
|
62
87
|
|
|
63
88
|
self.client_settings = {
|
|
64
89
|
'insert_block_size': int(self.settings.pop(
|
|
@@ -78,6 +103,18 @@ class Client(object):
|
|
|
78
103
|
),
|
|
79
104
|
'opentelemetry_tracestate': self.settings.pop(
|
|
80
105
|
'opentelemetry_tracestate', ''
|
|
106
|
+
),
|
|
107
|
+
'quota_key': self.settings.pop(
|
|
108
|
+
'quota_key', ''
|
|
109
|
+
),
|
|
110
|
+
'input_format_null_as_default': self.settings.pop(
|
|
111
|
+
'input_format_null_as_default', False
|
|
112
|
+
),
|
|
113
|
+
'namedtuple_as_json': self.settings.pop(
|
|
114
|
+
'namedtuple_as_json', True
|
|
115
|
+
),
|
|
116
|
+
'server_side_params': self.settings.pop(
|
|
117
|
+
'server_side_params', False
|
|
81
118
|
)
|
|
82
119
|
}
|
|
83
120
|
|
|
@@ -97,9 +134,33 @@ class Client(object):
|
|
|
97
134
|
self.iter_query_result_cls = IterQueryResult
|
|
98
135
|
self.progress_query_result_cls = ProgressQueryResult
|
|
99
136
|
|
|
100
|
-
|
|
101
|
-
self.
|
|
102
|
-
|
|
137
|
+
round_robin = kwargs.pop('round_robin', False)
|
|
138
|
+
self.connections = deque([Connection(*args, **kwargs)])
|
|
139
|
+
|
|
140
|
+
if round_robin and 'alt_hosts' in kwargs:
|
|
141
|
+
alt_hosts = kwargs.pop('alt_hosts')
|
|
142
|
+
for host in alt_hosts.split(','):
|
|
143
|
+
url = urlparse('clickhouse://' + host)
|
|
144
|
+
|
|
145
|
+
connection_kwargs = kwargs.copy()
|
|
146
|
+
num_args = len(args)
|
|
147
|
+
if num_args >= 2:
|
|
148
|
+
# host and port as positional arguments
|
|
149
|
+
connection_args = (url.hostname, url.port) + args[2:]
|
|
150
|
+
elif num_args >= 1:
|
|
151
|
+
# host as positional and port as keyword argument
|
|
152
|
+
connection_args = (url.hostname, ) + args[1:]
|
|
153
|
+
connection_kwargs['port'] = url.port
|
|
154
|
+
else:
|
|
155
|
+
# host and port as keyword arguments
|
|
156
|
+
connection_args = tuple()
|
|
157
|
+
connection_kwargs['host'] = url.hostname
|
|
158
|
+
connection_kwargs['port'] = url.port
|
|
159
|
+
|
|
160
|
+
connection = Connection(*connection_args, **connection_kwargs)
|
|
161
|
+
self.connections.append(connection)
|
|
162
|
+
|
|
163
|
+
self.connection = self.get_connection()
|
|
103
164
|
self.reset_last_query()
|
|
104
165
|
super(Client, self).__init__()
|
|
105
166
|
|
|
@@ -109,7 +170,22 @@ class Client(object):
|
|
|
109
170
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
110
171
|
self.disconnect()
|
|
111
172
|
|
|
173
|
+
def get_connection(self):
|
|
174
|
+
if hasattr(self, 'connection'):
|
|
175
|
+
self.connections.append(self.connection)
|
|
176
|
+
|
|
177
|
+
connection = self.connections.popleft()
|
|
178
|
+
|
|
179
|
+
connection.context.settings = self.settings
|
|
180
|
+
connection.context.client_settings = self.client_settings
|
|
181
|
+
return connection
|
|
182
|
+
|
|
112
183
|
def disconnect(self):
|
|
184
|
+
self.disconnect_connection()
|
|
185
|
+
for connection in self.connections:
|
|
186
|
+
connection.disconnect()
|
|
187
|
+
|
|
188
|
+
def disconnect_connection(self):
|
|
113
189
|
"""
|
|
114
190
|
Disconnects from the server.
|
|
115
191
|
"""
|
|
@@ -212,10 +288,33 @@ class Client(object):
|
|
|
212
288
|
if query.lower().startswith('use '):
|
|
213
289
|
self.connection.database = query[4:].strip()
|
|
214
290
|
|
|
291
|
+
def establish_connection(self, settings):
|
|
292
|
+
num_connections = len(self.connections)
|
|
293
|
+
if hasattr(self, 'connection'):
|
|
294
|
+
num_connections += 1
|
|
295
|
+
|
|
296
|
+
for i in range(num_connections):
|
|
297
|
+
try:
|
|
298
|
+
self.connection = self.get_connection()
|
|
299
|
+
self.make_query_settings(settings)
|
|
300
|
+
self.connection.force_connect()
|
|
301
|
+
self.last_query = QueryInfo()
|
|
302
|
+
|
|
303
|
+
except (errors.SocketTimeoutError, errors.NetworkError):
|
|
304
|
+
if i < num_connections - 1:
|
|
305
|
+
continue
|
|
306
|
+
raise
|
|
307
|
+
|
|
308
|
+
return
|
|
309
|
+
|
|
215
310
|
@contextmanager
|
|
216
|
-
def disconnect_on_error(self, query):
|
|
311
|
+
def disconnect_on_error(self, query, settings):
|
|
217
312
|
try:
|
|
313
|
+
self.establish_connection(settings)
|
|
314
|
+
self.connection.server_info.session_timezone = None
|
|
315
|
+
|
|
218
316
|
yield
|
|
317
|
+
|
|
219
318
|
self.track_current_database(query)
|
|
220
319
|
|
|
221
320
|
except (Exception, KeyboardInterrupt):
|
|
@@ -267,11 +366,8 @@ class Client(object):
|
|
|
267
366
|
"""
|
|
268
367
|
|
|
269
368
|
start_time = time()
|
|
270
|
-
self.make_query_settings(settings)
|
|
271
|
-
self.connection.force_connect()
|
|
272
|
-
self.last_query = QueryInfo()
|
|
273
369
|
|
|
274
|
-
with self.disconnect_on_error(query):
|
|
370
|
+
with self.disconnect_on_error(query, settings):
|
|
275
371
|
# INSERT queries can use list/tuple/generator of list/tuples/dicts.
|
|
276
372
|
# For SELECT parameters can be passed in only in dict right now.
|
|
277
373
|
is_insert = isinstance(params, (list, tuple, types.GeneratorType))
|
|
@@ -322,11 +418,7 @@ class Client(object):
|
|
|
322
418
|
:return: :ref:`progress-query-result` proxy.
|
|
323
419
|
"""
|
|
324
420
|
|
|
325
|
-
self.
|
|
326
|
-
self.connection.force_connect()
|
|
327
|
-
self.last_query = QueryInfo()
|
|
328
|
-
|
|
329
|
-
with self.disconnect_on_error(query):
|
|
421
|
+
with self.disconnect_on_error(query, settings):
|
|
330
422
|
return self.process_ordinary_query_with_progress(
|
|
331
423
|
query, params=params, with_column_types=with_column_types,
|
|
332
424
|
external_tables=external_tables, query_id=query_id,
|
|
@@ -336,7 +428,7 @@ class Client(object):
|
|
|
336
428
|
def execute_iter(
|
|
337
429
|
self, query, params=None, with_column_types=False,
|
|
338
430
|
external_tables=None, query_id=None, settings=None,
|
|
339
|
-
types_check=False):
|
|
431
|
+
types_check=False, chunk_size=1):
|
|
340
432
|
"""
|
|
341
433
|
*New in version 0.0.14.*
|
|
342
434
|
|
|
@@ -358,23 +450,20 @@ class Client(object):
|
|
|
358
450
|
Defaults to ``None`` (no additional settings).
|
|
359
451
|
:param types_check: enables type checking of data for INSERT queries.
|
|
360
452
|
Causes additional overhead. Defaults to ``False``.
|
|
453
|
+
:param chunk_size: chunk query results.
|
|
361
454
|
:return: :ref:`iter-query-result` proxy.
|
|
362
455
|
"""
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
self.connection.force_connect()
|
|
366
|
-
self.last_query = QueryInfo()
|
|
367
|
-
|
|
368
|
-
with self.disconnect_on_error(query):
|
|
369
|
-
return self.iter_process_ordinary_query(
|
|
456
|
+
with self.disconnect_on_error(query, settings):
|
|
457
|
+
rv = self.iter_process_ordinary_query(
|
|
370
458
|
query, params=params, with_column_types=with_column_types,
|
|
371
459
|
external_tables=external_tables,
|
|
372
460
|
query_id=query_id, types_check=types_check
|
|
373
461
|
)
|
|
462
|
+
return chunks(rv, chunk_size) if chunk_size > 1 else rv
|
|
374
463
|
|
|
375
464
|
def query_dataframe(
|
|
376
465
|
self, query, params=None, external_tables=None, query_id=None,
|
|
377
|
-
settings=None):
|
|
466
|
+
settings=None, replace_nonwords=True):
|
|
378
467
|
"""
|
|
379
468
|
*New in version 0.2.0.*
|
|
380
469
|
|
|
@@ -389,6 +478,8 @@ class Client(object):
|
|
|
389
478
|
ClickHouse server will generate it.
|
|
390
479
|
:param settings: dictionary of query settings.
|
|
391
480
|
Defaults to ``None`` (no additional settings).
|
|
481
|
+
:param replace_nonwords: boolean to replace non-words in column names
|
|
482
|
+
to underscores. Defaults to ``True``.
|
|
392
483
|
:return: pandas DataFrame.
|
|
393
484
|
"""
|
|
394
485
|
|
|
@@ -403,8 +494,12 @@ class Client(object):
|
|
|
403
494
|
settings=settings
|
|
404
495
|
)
|
|
405
496
|
|
|
497
|
+
columns = [name for name, type_ in columns]
|
|
498
|
+
if replace_nonwords:
|
|
499
|
+
columns = [re.sub(r'\W', '_', x) for x in columns]
|
|
500
|
+
|
|
406
501
|
return pd.DataFrame(
|
|
407
|
-
{
|
|
502
|
+
{col: d for d, col in zip(data, columns)}, columns=columns
|
|
408
503
|
)
|
|
409
504
|
|
|
410
505
|
def insert_dataframe(
|
|
@@ -432,11 +527,8 @@ class Client(object):
|
|
|
432
527
|
raise RuntimeError('Extras for NumPy must be installed')
|
|
433
528
|
|
|
434
529
|
start_time = time()
|
|
435
|
-
self.make_query_settings(settings)
|
|
436
|
-
self.connection.force_connect()
|
|
437
|
-
self.last_query = QueryInfo()
|
|
438
530
|
|
|
439
|
-
with self.disconnect_on_error(query):
|
|
531
|
+
with self.disconnect_on_error(query, settings):
|
|
440
532
|
self.connection.send_query(query, query_id=query_id)
|
|
441
533
|
self.connection.send_external_tables(external_tables)
|
|
442
534
|
|
|
@@ -444,6 +536,12 @@ class Client(object):
|
|
|
444
536
|
rv = None
|
|
445
537
|
if sample_block:
|
|
446
538
|
columns = [x[0] for x in sample_block.columns_with_types]
|
|
539
|
+
# raise if any columns are missing from the dataframe
|
|
540
|
+
diff = set(columns) - set(dataframe.columns)
|
|
541
|
+
if len(diff):
|
|
542
|
+
msg = "DataFrame missing required columns: {}"
|
|
543
|
+
raise ValueError(msg.format(list(diff)))
|
|
544
|
+
|
|
447
545
|
data = [dataframe[column].values for column in columns]
|
|
448
546
|
rv = self.send_data(sample_block, data, columnar=True)
|
|
449
547
|
self.receive_end_of_query()
|
|
@@ -457,9 +555,11 @@ class Client(object):
|
|
|
457
555
|
types_check=False, columnar=False):
|
|
458
556
|
|
|
459
557
|
if params is not None:
|
|
460
|
-
query = self.substitute_params(
|
|
558
|
+
query = self.substitute_params(
|
|
559
|
+
query, params, self.connection.context
|
|
560
|
+
)
|
|
461
561
|
|
|
462
|
-
self.connection.send_query(query, query_id=query_id)
|
|
562
|
+
self.connection.send_query(query, query_id=query_id, params=params)
|
|
463
563
|
self.connection.send_external_tables(external_tables,
|
|
464
564
|
types_check=types_check)
|
|
465
565
|
return self.receive_result(with_column_types=with_column_types,
|
|
@@ -471,9 +571,10 @@ class Client(object):
|
|
|
471
571
|
types_check=False, columnar=False):
|
|
472
572
|
|
|
473
573
|
if params is not None:
|
|
474
|
-
query = self.substitute_params(
|
|
475
|
-
|
|
476
|
-
|
|
574
|
+
query = self.substitute_params(
|
|
575
|
+
query, params, self.connection.context
|
|
576
|
+
)
|
|
577
|
+
self.connection.send_query(query, query_id=query_id, params=params)
|
|
477
578
|
self.connection.send_external_tables(external_tables,
|
|
478
579
|
types_check=types_check)
|
|
479
580
|
return self.receive_result(with_column_types=with_column_types,
|
|
@@ -485,9 +586,11 @@ class Client(object):
|
|
|
485
586
|
types_check=False):
|
|
486
587
|
|
|
487
588
|
if params is not None:
|
|
488
|
-
query = self.substitute_params(
|
|
589
|
+
query = self.substitute_params(
|
|
590
|
+
query, params, self.connection.context
|
|
591
|
+
)
|
|
489
592
|
|
|
490
|
-
self.connection.send_query(query, query_id=query_id)
|
|
593
|
+
self.connection.send_query(query, query_id=query_id, params=params)
|
|
491
594
|
self.connection.send_external_tables(external_tables,
|
|
492
595
|
types_check=types_check)
|
|
493
596
|
return self.iter_receive_result(with_column_types=with_column_types)
|
|
@@ -498,12 +601,12 @@ class Client(object):
|
|
|
498
601
|
self.connection.send_query(query_without_data, query_id=query_id)
|
|
499
602
|
self.connection.send_external_tables(external_tables,
|
|
500
603
|
types_check=types_check)
|
|
501
|
-
|
|
502
604
|
sample_block = self.receive_sample_block()
|
|
605
|
+
|
|
503
606
|
if sample_block:
|
|
504
607
|
rv = self.send_data(sample_block, data,
|
|
505
608
|
types_check=types_check, columnar=columnar)
|
|
506
|
-
self.
|
|
609
|
+
self.receive_end_of_insert_query()
|
|
507
610
|
return rv
|
|
508
611
|
|
|
509
612
|
def receive_sample_block(self):
|
|
@@ -557,8 +660,15 @@ class Client(object):
|
|
|
557
660
|
self.connection.send_data(block)
|
|
558
661
|
inserted_rows += block.num_rows
|
|
559
662
|
|
|
663
|
+
# Starting from the specific revision there are profile events
|
|
664
|
+
# sent by server in response to each inserted block
|
|
665
|
+
self.receive_profile_events()
|
|
666
|
+
|
|
560
667
|
# Empty block means end of data.
|
|
561
668
|
self.connection.send_data(block_cls())
|
|
669
|
+
# If enabled by revision profile events are also sent after empty block
|
|
670
|
+
self.receive_profile_events()
|
|
671
|
+
|
|
562
672
|
return inserted_rows
|
|
563
673
|
|
|
564
674
|
def receive_end_of_query(self):
|
|
@@ -568,6 +678,9 @@ class Client(object):
|
|
|
568
678
|
if packet.type == ServerPacketTypes.END_OF_STREAM:
|
|
569
679
|
break
|
|
570
680
|
|
|
681
|
+
elif packet.type == ServerPacketTypes.PROGRESS:
|
|
682
|
+
self.last_query.store_progress(packet.progress)
|
|
683
|
+
|
|
571
684
|
elif packet.type == ServerPacketTypes.EXCEPTION:
|
|
572
685
|
raise packet.exception
|
|
573
686
|
|
|
@@ -577,116 +690,123 @@ class Client(object):
|
|
|
577
690
|
elif packet.type == ServerPacketTypes.TABLE_COLUMNS:
|
|
578
691
|
pass
|
|
579
692
|
|
|
693
|
+
elif packet.type == ServerPacketTypes.PROFILE_EVENTS:
|
|
694
|
+
self.last_query.store_profile(packet.profile_info)
|
|
695
|
+
|
|
580
696
|
else:
|
|
581
697
|
message = self.connection.unexpected_packet_message(
|
|
582
|
-
'Exception, EndOfStream
|
|
698
|
+
'Exception, EndOfStream, Progress, TableColumns, '
|
|
699
|
+
'ProfileEvents or Log', packet.type
|
|
583
700
|
)
|
|
584
701
|
raise errors.UnexpectedPacketFromServerError(message)
|
|
585
702
|
|
|
586
|
-
def
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
# Client must still read until END_OF_STREAM packet.
|
|
590
|
-
return self.receive_result(with_column_types=with_column_types)
|
|
703
|
+
def receive_end_of_insert_query(self):
|
|
704
|
+
while True:
|
|
705
|
+
packet = self.connection.receive_packet()
|
|
591
706
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
raise ValueError('Parameters are expected in dict form')
|
|
707
|
+
if packet.type == ServerPacketTypes.END_OF_STREAM:
|
|
708
|
+
break
|
|
595
709
|
|
|
596
|
-
|
|
597
|
-
|
|
710
|
+
elif packet.type == ServerPacketTypes.LOG:
|
|
711
|
+
log_block(packet.block)
|
|
598
712
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
"""
|
|
602
|
-
Return a client configured from the given URL.
|
|
713
|
+
elif packet.type == ServerPacketTypes.PROGRESS:
|
|
714
|
+
self.last_query.store_progress(packet.progress)
|
|
603
715
|
|
|
604
|
-
|
|
716
|
+
elif packet.type == ServerPacketTypes.EXCEPTION:
|
|
717
|
+
raise packet.exception
|
|
605
718
|
|
|
606
|
-
|
|
607
|
-
|
|
719
|
+
else:
|
|
720
|
+
message = self.connection.unexpected_packet_message(
|
|
721
|
+
'EndOfStream, Log, Progress or Exception', packet.type
|
|
722
|
+
)
|
|
723
|
+
raise errors.UnexpectedPacketFromServerError(message)
|
|
608
724
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
725
|
+
def receive_profile_events(self):
|
|
726
|
+
revision = self.connection.server_info.used_revision
|
|
727
|
+
if (
|
|
728
|
+
revision <
|
|
729
|
+
defines.DBMS_MIN_PROTOCOL_VERSION_WITH_PROFILE_EVENTS_IN_INSERT
|
|
730
|
+
):
|
|
731
|
+
return None
|
|
612
732
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
"""
|
|
616
|
-
url = urlparse(url)
|
|
733
|
+
while True:
|
|
734
|
+
packet = self.connection.receive_packet()
|
|
617
735
|
|
|
618
|
-
|
|
619
|
-
|
|
736
|
+
if packet.type == ServerPacketTypes.PROFILE_EVENTS:
|
|
737
|
+
self.last_query.store_profile(packet.profile_info)
|
|
738
|
+
break
|
|
620
739
|
|
|
621
|
-
|
|
740
|
+
elif packet.type == ServerPacketTypes.PROGRESS:
|
|
741
|
+
self.last_query.store_progress(packet.progress)
|
|
622
742
|
|
|
623
|
-
|
|
624
|
-
|
|
743
|
+
elif packet.type == ServerPacketTypes.LOG:
|
|
744
|
+
log_block(packet.block)
|
|
625
745
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
kwargs['database'] = path
|
|
746
|
+
elif packet.type == ServerPacketTypes.EXCEPTION:
|
|
747
|
+
raise packet.exception
|
|
629
748
|
|
|
630
|
-
|
|
631
|
-
|
|
749
|
+
elif packet.type == ServerPacketTypes.TIMEZONE_UPDATE:
|
|
750
|
+
pass
|
|
632
751
|
|
|
633
|
-
|
|
634
|
-
|
|
752
|
+
else:
|
|
753
|
+
message = self.connection.unexpected_packet_message(
|
|
754
|
+
'ProfileEvents, Progress, Log, Exception or '
|
|
755
|
+
'TimezoneUpdate', packet.type
|
|
756
|
+
)
|
|
757
|
+
raise errors.UnexpectedPacketFromServerError(message)
|
|
635
758
|
|
|
636
|
-
|
|
637
|
-
|
|
759
|
+
def cancel(self, with_column_types=False):
|
|
760
|
+
# TODO: Add warning if already cancelled.
|
|
761
|
+
self.connection.send_cancel()
|
|
762
|
+
# Client must still read until END_OF_STREAM packet.
|
|
763
|
+
return self.receive_result(with_column_types=with_column_types)
|
|
638
764
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
'send_receive_timeout',
|
|
643
|
-
'sync_request_timeout'
|
|
644
|
-
}
|
|
765
|
+
def substitute_params(self, query, params, context):
|
|
766
|
+
"""
|
|
767
|
+
Substitutes parameters into a provided query.
|
|
645
768
|
|
|
646
|
-
|
|
647
|
-
if not value or not len(value):
|
|
648
|
-
continue
|
|
769
|
+
For example::
|
|
649
770
|
|
|
650
|
-
|
|
771
|
+
client = Client(...)
|
|
651
772
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
773
|
+
substituted_query = client.substitute_params(
|
|
774
|
+
query='SELECT 1234, %(foo)s',
|
|
775
|
+
params={'foo': 'bar'},
|
|
776
|
+
context=client.connection.context
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
# prints: SELECT 1234, 'bar'
|
|
780
|
+
print(substituted_query)
|
|
781
|
+
"""
|
|
782
|
+
# In case of server side templating we don't substitute here.
|
|
783
|
+
if self.connection.context.client_settings['server_side_params']:
|
|
784
|
+
return query
|
|
658
785
|
|
|
659
|
-
|
|
660
|
-
|
|
786
|
+
if not isinstance(params, dict):
|
|
787
|
+
raise ValueError('Parameters are expected in dict form')
|
|
661
788
|
|
|
662
|
-
|
|
663
|
-
|
|
789
|
+
escaped = escape_params(params, context)
|
|
790
|
+
return query % escaped
|
|
664
791
|
|
|
665
|
-
|
|
666
|
-
|
|
792
|
+
@classmethod
|
|
793
|
+
def from_url(cls, url):
|
|
794
|
+
"""
|
|
795
|
+
Return a client configured from the given URL.
|
|
667
796
|
|
|
668
|
-
|
|
669
|
-
kwargs[name] = float(value)
|
|
797
|
+
For example::
|
|
670
798
|
|
|
671
|
-
|
|
672
|
-
|
|
799
|
+
clickhouse://[user:password]@localhost:9000/default
|
|
800
|
+
clickhouses://[user:password]@localhost:9440/default
|
|
673
801
|
|
|
674
|
-
|
|
675
|
-
kwargs[name] = asbool(value)
|
|
802
|
+
Three URL schemes are supported:
|
|
676
803
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
kwargs[name] = asbool(value)
|
|
680
|
-
elif name == 'ssl_version':
|
|
681
|
-
kwargs[name] = getattr(ssl, value)
|
|
682
|
-
elif name in ['ca_certs', 'ciphers', 'keyfile', 'certfile']:
|
|
683
|
-
kwargs[name] = value
|
|
684
|
-
elif name == 'alt_hosts':
|
|
685
|
-
kwargs['alt_hosts'] = value
|
|
686
|
-
else:
|
|
687
|
-
settings[name] = value
|
|
804
|
+
* clickhouse:// creates a normal TCP socket connection
|
|
805
|
+
* clickhouses:// creates a SSL wrapped TCP socket connection
|
|
688
806
|
|
|
689
|
-
|
|
690
|
-
|
|
807
|
+
Any additional querystring arguments will be passed along to
|
|
808
|
+
the Connection class's initializer.
|
|
809
|
+
"""
|
|
810
|
+
host, kwargs = parse_url(url)
|
|
691
811
|
|
|
692
812
|
return cls(host, **kwargs)
|