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.
Files changed (52) hide show
  1. clickhouse_driver/__init__.py +1 -1
  2. clickhouse_driver/block.py +32 -3
  3. clickhouse_driver/bufferedreader.cpython-38-darwin.so +0 -0
  4. clickhouse_driver/bufferedwriter.cpython-38-darwin.so +0 -0
  5. clickhouse_driver/client.py +243 -123
  6. clickhouse_driver/clientinfo.py +24 -4
  7. clickhouse_driver/columns/arraycolumn.py +19 -8
  8. clickhouse_driver/columns/base.py +81 -7
  9. clickhouse_driver/columns/boolcolumn.py +7 -0
  10. clickhouse_driver/columns/datecolumn.py +52 -13
  11. clickhouse_driver/columns/datetimecolumn.py +5 -11
  12. clickhouse_driver/columns/decimalcolumn.py +4 -6
  13. clickhouse_driver/columns/enumcolumn.py +27 -17
  14. clickhouse_driver/columns/jsoncolumn.py +37 -0
  15. clickhouse_driver/columns/largeint.cpython-38-darwin.so +0 -0
  16. clickhouse_driver/columns/lowcardinalitycolumn.py +25 -6
  17. clickhouse_driver/columns/mapcolumn.py +19 -4
  18. clickhouse_driver/columns/nestedcolumn.py +10 -0
  19. clickhouse_driver/columns/numpy/boolcolumn.py +8 -0
  20. clickhouse_driver/columns/numpy/datetimecolumn.py +24 -27
  21. clickhouse_driver/columns/numpy/lowcardinalitycolumn.py +5 -4
  22. clickhouse_driver/columns/numpy/service.py +19 -41
  23. clickhouse_driver/columns/numpy/tuplecolumn.py +37 -0
  24. clickhouse_driver/columns/service.py +71 -17
  25. clickhouse_driver/columns/tuplecolumn.py +32 -34
  26. clickhouse_driver/columns/util.py +61 -0
  27. clickhouse_driver/columns/uuidcolumn.py +1 -1
  28. clickhouse_driver/compression/base.py +39 -4
  29. clickhouse_driver/compression/lz4.py +4 -38
  30. clickhouse_driver/compression/zstd.py +4 -35
  31. clickhouse_driver/connection.py +182 -27
  32. clickhouse_driver/dbapi/connection.py +6 -3
  33. clickhouse_driver/dbapi/cursor.py +3 -1
  34. clickhouse_driver/dbapi/extras.py +73 -0
  35. clickhouse_driver/defines.py +17 -2
  36. clickhouse_driver/errors.py +7 -0
  37. clickhouse_driver/log.py +7 -3
  38. clickhouse_driver/numpy/helpers.py +5 -2
  39. clickhouse_driver/progress.py +15 -3
  40. clickhouse_driver/protocol.py +28 -3
  41. clickhouse_driver/settings/writer.py +7 -2
  42. clickhouse_driver/streams/native.py +25 -7
  43. clickhouse_driver/util/compat.py +39 -0
  44. clickhouse_driver/util/escape.py +47 -8
  45. clickhouse_driver/util/helpers.py +114 -0
  46. clickhouse_driver/varint.cpython-38-darwin.so +0 -0
  47. {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/METADATA +15 -15
  48. clickhouse_driver-0.2.9.dist-info/RECORD +89 -0
  49. {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/WHEEL +1 -1
  50. clickhouse_driver-0.2.2.dist-info/RECORD +0 -81
  51. {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/LICENSE +0 -0
  52. {clickhouse_driver-0.2.2.dist-info → clickhouse_driver-0.2.9.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ from .client import Client
3
3
  from .dbapi import connect
4
4
 
5
5
 
6
- VERSION = (0, 2, 2)
6
+ VERSION = (0, 2, 9)
7
7
  __version__ = '.'.join(str(x) for x in VERSION)
8
8
 
9
9
  __all__ = ['Client', 'connect']
@@ -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
- data[i] = [row[name] for name in column_names]
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)
@@ -1,9 +1,9 @@
1
1
  import re
2
- import ssl
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, parse_qs, unquote
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, asbool
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.tech/docs/en/operations/settings/settings/>`_.
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 numpy for columns reading. New in version
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
- self.connection = Connection(*args, **kwargs)
101
- self.connection.context.settings = self.settings
102
- self.connection.context.client_settings = self.client_settings
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.make_query_settings(settings)
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
- self.make_query_settings(settings)
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
- {re.sub(r'\W', '_', col[0]): d for d, col in zip(data, columns)}
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(query, 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(query, params)
475
-
476
- self.connection.send_query(query, query_id=query_id)
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(query, 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.receive_end_of_query()
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 or Log', packet.type
698
+ 'Exception, EndOfStream, Progress, TableColumns, '
699
+ 'ProfileEvents or Log', packet.type
583
700
  )
584
701
  raise errors.UnexpectedPacketFromServerError(message)
585
702
 
586
- def cancel(self, with_column_types=False):
587
- # TODO: Add warning if already cancelled.
588
- self.connection.send_cancel()
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
- def substitute_params(self, query, params):
593
- if not isinstance(params, dict):
594
- raise ValueError('Parameters are expected in dict form')
707
+ if packet.type == ServerPacketTypes.END_OF_STREAM:
708
+ break
595
709
 
596
- escaped = escape_params(params)
597
- return query % escaped
710
+ elif packet.type == ServerPacketTypes.LOG:
711
+ log_block(packet.block)
598
712
 
599
- @classmethod
600
- def from_url(cls, url):
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
- For example::
716
+ elif packet.type == ServerPacketTypes.EXCEPTION:
717
+ raise packet.exception
605
718
 
606
- clickhouse://[user:password]@localhost:9000/default
607
- clickhouses://[user:password]@localhost:9440/default
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
- Three URL schemes are supported:
610
- clickhouse:// creates a normal TCP socket connection
611
- clickhouses:// creates a SSL wrapped TCP socket connection
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
- Any additional querystring arguments will be passed along to
614
- the Connection class's initializer.
615
- """
616
- url = urlparse(url)
733
+ while True:
734
+ packet = self.connection.receive_packet()
617
735
 
618
- settings = {}
619
- kwargs = {}
736
+ if packet.type == ServerPacketTypes.PROFILE_EVENTS:
737
+ self.last_query.store_profile(packet.profile_info)
738
+ break
620
739
 
621
- host = url.hostname
740
+ elif packet.type == ServerPacketTypes.PROGRESS:
741
+ self.last_query.store_progress(packet.progress)
622
742
 
623
- if url.port is not None:
624
- kwargs['port'] = url.port
743
+ elif packet.type == ServerPacketTypes.LOG:
744
+ log_block(packet.block)
625
745
 
626
- path = url.path.replace('/', '', 1)
627
- if path:
628
- kwargs['database'] = path
746
+ elif packet.type == ServerPacketTypes.EXCEPTION:
747
+ raise packet.exception
629
748
 
630
- if url.username is not None:
631
- kwargs['user'] = unquote(url.username)
749
+ elif packet.type == ServerPacketTypes.TIMEZONE_UPDATE:
750
+ pass
632
751
 
633
- if url.password is not None:
634
- kwargs['password'] = unquote(url.password)
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
- if url.scheme == 'clickhouses':
637
- kwargs['secure'] = True
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
- compression_algs = {'lz4', 'lz4hc', 'zstd'}
640
- timeouts = {
641
- 'connect_timeout',
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
- for name, value in parse_qs(url.query).items():
647
- if not value or not len(value):
648
- continue
769
+ For example::
649
770
 
650
- value = value[0]
771
+ client = Client(...)
651
772
 
652
- if name == 'compression':
653
- value = value.lower()
654
- if value in compression_algs:
655
- kwargs[name] = value
656
- else:
657
- kwargs[name] = asbool(value)
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
- elif name == 'secure':
660
- kwargs[name] = asbool(value)
786
+ if not isinstance(params, dict):
787
+ raise ValueError('Parameters are expected in dict form')
661
788
 
662
- elif name == 'use_numpy':
663
- settings[name] = asbool(value)
789
+ escaped = escape_params(params, context)
790
+ return query % escaped
664
791
 
665
- elif name == 'client_name':
666
- kwargs[name] = value
792
+ @classmethod
793
+ def from_url(cls, url):
794
+ """
795
+ Return a client configured from the given URL.
667
796
 
668
- elif name in timeouts:
669
- kwargs[name] = float(value)
797
+ For example::
670
798
 
671
- elif name == 'compress_block_size':
672
- kwargs[name] = int(value)
799
+ clickhouse://[user:password]@localhost:9000/default
800
+ clickhouses://[user:password]@localhost:9440/default
673
801
 
674
- elif name == 'settings_is_important':
675
- kwargs[name] = asbool(value)
802
+ Three URL schemes are supported:
676
803
 
677
- # ssl
678
- elif name == 'verify':
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
- if settings:
690
- kwargs['settings'] = settings
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)