clickhouse-driver 0.2.3__cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl → 0.2.9__cp310-cp310-manylinux_2_17_s390x.manylinux2014_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.
Files changed (45) hide show
  1. clickhouse_driver/__init__.py +1 -1
  2. clickhouse_driver/block.py +3 -2
  3. clickhouse_driver/bufferedreader.cpython-310-s390x-linux-gnu.so +0 -0
  4. clickhouse_driver/bufferedwriter.cpython-310-s390x-linux-gnu.so +0 -0
  5. clickhouse_driver/client.py +217 -102
  6. clickhouse_driver/clientinfo.py +22 -2
  7. clickhouse_driver/columns/arraycolumn.py +19 -8
  8. clickhouse_driver/columns/base.py +81 -7
  9. clickhouse_driver/columns/datecolumn.py +52 -13
  10. clickhouse_driver/columns/datetimecolumn.py +3 -2
  11. clickhouse_driver/columns/decimalcolumn.py +0 -4
  12. clickhouse_driver/columns/enumcolumn.py +27 -17
  13. clickhouse_driver/columns/jsoncolumn.py +37 -0
  14. clickhouse_driver/columns/largeint.cpython-310-s390x-linux-gnu.so +0 -0
  15. clickhouse_driver/columns/lowcardinalitycolumn.py +25 -6
  16. clickhouse_driver/columns/mapcolumn.py +19 -4
  17. clickhouse_driver/columns/nestedcolumn.py +4 -67
  18. clickhouse_driver/columns/numpy/boolcolumn.py +8 -0
  19. clickhouse_driver/columns/numpy/datetimecolumn.py +23 -20
  20. clickhouse_driver/columns/numpy/lowcardinalitycolumn.py +5 -4
  21. clickhouse_driver/columns/numpy/service.py +19 -3
  22. clickhouse_driver/columns/numpy/tuplecolumn.py +37 -0
  23. clickhouse_driver/columns/service.py +48 -11
  24. clickhouse_driver/columns/tuplecolumn.py +32 -34
  25. clickhouse_driver/columns/util.py +61 -0
  26. clickhouse_driver/columns/uuidcolumn.py +1 -1
  27. clickhouse_driver/connection.py +151 -25
  28. clickhouse_driver/dbapi/connection.py +6 -3
  29. clickhouse_driver/dbapi/extras.py +73 -0
  30. clickhouse_driver/defines.py +17 -2
  31. clickhouse_driver/log.py +7 -3
  32. clickhouse_driver/numpy/helpers.py +5 -2
  33. clickhouse_driver/progress.py +15 -3
  34. clickhouse_driver/protocol.py +28 -3
  35. clickhouse_driver/settings/writer.py +7 -2
  36. clickhouse_driver/streams/native.py +25 -7
  37. clickhouse_driver/util/compat.py +12 -0
  38. clickhouse_driver/util/escape.py +36 -8
  39. clickhouse_driver/util/helpers.py +114 -0
  40. clickhouse_driver/varint.cpython-310-s390x-linux-gnu.so +0 -0
  41. {clickhouse_driver-0.2.3.dist-info → clickhouse_driver-0.2.9.dist-info}/METADATA +10 -13
  42. {clickhouse_driver-0.2.3.dist-info → clickhouse_driver-0.2.9.dist-info}/RECORD +76 -71
  43. {clickhouse_driver-0.2.3.dist-info → clickhouse_driver-0.2.9.dist-info}/WHEEL +1 -1
  44. {clickhouse_driver-0.2.3.dist-info → clickhouse_driver-0.2.9.dist-info}/LICENSE +0 -0
  45. {clickhouse_driver-0.2.3.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, 3)
6
+ VERSION = (0, 2, 9)
7
7
  __version__ = '.'.join(str(x) for x in VERSION)
8
8
 
9
9
  __all__ = ['Client', 'connect']
@@ -1,7 +1,7 @@
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
4
- from .columns import nestedcolumn
5
5
 
6
6
 
7
7
  class BlockInfo(object):
@@ -172,7 +172,8 @@ class RowOrientedBlock(BaseBlock):
172
172
  for name, type_ in columns_with_types:
173
173
  cwt = None
174
174
  if type_.startswith('Nested'):
175
- cwt = nestedcolumn.get_columns_with_types(type_)
175
+ inner_spec = get_inner_spec('Nested', type_)
176
+ cwt = get_inner_columns_with_types(inner_spec)
176
177
  columns_with_cwt.append((name, cwt))
177
178
 
178
179
  for i, row in enumerate(data):
@@ -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.
@@ -49,6 +49,24 @@ class Client(object):
49
49
  * ``quota_key`` -- A string to differentiate quotas when the user have
50
50
  keyed quotas configured on server.
51
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*.
52
70
  """
53
71
 
54
72
  available_client_settings = (
@@ -58,7 +76,10 @@ class Client(object):
58
76
  'use_numpy',
59
77
  'opentelemetry_traceparent',
60
78
  'opentelemetry_tracestate',
61
- 'quota_key'
79
+ 'quota_key',
80
+ 'input_format_null_as_default',
81
+ 'namedtuple_as_json',
82
+ 'server_side_params'
62
83
  )
63
84
 
64
85
  def __init__(self, *args, **kwargs):
@@ -85,6 +106,15 @@ class Client(object):
85
106
  ),
86
107
  'quota_key': self.settings.pop(
87
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
88
118
  )
89
119
  }
90
120
 
@@ -104,9 +134,33 @@ class Client(object):
104
134
  self.iter_query_result_cls = IterQueryResult
105
135
  self.progress_query_result_cls = ProgressQueryResult
106
136
 
107
- self.connection = Connection(*args, **kwargs)
108
- self.connection.context.settings = self.settings
109
- 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()
110
164
  self.reset_last_query()
111
165
  super(Client, self).__init__()
112
166
 
@@ -116,7 +170,22 @@ class Client(object):
116
170
  def __exit__(self, exc_type, exc_val, exc_tb):
117
171
  self.disconnect()
118
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
+
119
183
  def disconnect(self):
184
+ self.disconnect_connection()
185
+ for connection in self.connections:
186
+ connection.disconnect()
187
+
188
+ def disconnect_connection(self):
120
189
  """
121
190
  Disconnects from the server.
122
191
  """
@@ -219,13 +288,30 @@ class Client(object):
219
288
  if query.lower().startswith('use '):
220
289
  self.connection.database = query[4:].strip()
221
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
+
222
310
  @contextmanager
223
311
  def disconnect_on_error(self, query, settings):
224
- self.make_query_settings(settings)
225
-
226
312
  try:
227
- self.connection.force_connect()
228
- self.last_query = QueryInfo()
313
+ self.establish_connection(settings)
314
+ self.connection.server_info.session_timezone = None
229
315
 
230
316
  yield
231
317
 
@@ -342,7 +428,7 @@ class Client(object):
342
428
  def execute_iter(
343
429
  self, query, params=None, with_column_types=False,
344
430
  external_tables=None, query_id=None, settings=None,
345
- types_check=False):
431
+ types_check=False, chunk_size=1):
346
432
  """
347
433
  *New in version 0.0.14.*
348
434
 
@@ -364,19 +450,20 @@ class Client(object):
364
450
  Defaults to ``None`` (no additional settings).
365
451
  :param types_check: enables type checking of data for INSERT queries.
366
452
  Causes additional overhead. Defaults to ``False``.
453
+ :param chunk_size: chunk query results.
367
454
  :return: :ref:`iter-query-result` proxy.
368
455
  """
369
-
370
456
  with self.disconnect_on_error(query, settings):
371
- return self.iter_process_ordinary_query(
457
+ rv = self.iter_process_ordinary_query(
372
458
  query, params=params, with_column_types=with_column_types,
373
459
  external_tables=external_tables,
374
460
  query_id=query_id, types_check=types_check
375
461
  )
462
+ return chunks(rv, chunk_size) if chunk_size > 1 else rv
376
463
 
377
464
  def query_dataframe(
378
465
  self, query, params=None, external_tables=None, query_id=None,
379
- settings=None):
466
+ settings=None, replace_nonwords=True):
380
467
  """
381
468
  *New in version 0.2.0.*
382
469
 
@@ -391,6 +478,8 @@ class Client(object):
391
478
  ClickHouse server will generate it.
392
479
  :param settings: dictionary of query settings.
393
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``.
394
483
  :return: pandas DataFrame.
395
484
  """
396
485
 
@@ -405,8 +494,12 @@ class Client(object):
405
494
  settings=settings
406
495
  )
407
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
+
408
501
  return pd.DataFrame(
409
- {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
410
503
  )
411
504
 
412
505
  def insert_dataframe(
@@ -443,6 +536,12 @@ class Client(object):
443
536
  rv = None
444
537
  if sample_block:
445
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
+
446
545
  data = [dataframe[column].values for column in columns]
447
546
  rv = self.send_data(sample_block, data, columnar=True)
448
547
  self.receive_end_of_query()
@@ -460,7 +559,7 @@ class Client(object):
460
559
  query, params, self.connection.context
461
560
  )
462
561
 
463
- self.connection.send_query(query, query_id=query_id)
562
+ self.connection.send_query(query, query_id=query_id, params=params)
464
563
  self.connection.send_external_tables(external_tables,
465
564
  types_check=types_check)
466
565
  return self.receive_result(with_column_types=with_column_types,
@@ -475,8 +574,7 @@ class Client(object):
475
574
  query = self.substitute_params(
476
575
  query, params, self.connection.context
477
576
  )
478
-
479
- self.connection.send_query(query, query_id=query_id)
577
+ self.connection.send_query(query, query_id=query_id, params=params)
480
578
  self.connection.send_external_tables(external_tables,
481
579
  types_check=types_check)
482
580
  return self.receive_result(with_column_types=with_column_types,
@@ -492,7 +590,7 @@ class Client(object):
492
590
  query, params, self.connection.context
493
591
  )
494
592
 
495
- self.connection.send_query(query, query_id=query_id)
593
+ self.connection.send_query(query, query_id=query_id, params=params)
496
594
  self.connection.send_external_tables(external_tables,
497
595
  types_check=types_check)
498
596
  return self.iter_receive_result(with_column_types=with_column_types)
@@ -503,12 +601,12 @@ class Client(object):
503
601
  self.connection.send_query(query_without_data, query_id=query_id)
504
602
  self.connection.send_external_tables(external_tables,
505
603
  types_check=types_check)
506
-
507
604
  sample_block = self.receive_sample_block()
605
+
508
606
  if sample_block:
509
607
  rv = self.send_data(sample_block, data,
510
608
  types_check=types_check, columnar=columnar)
511
- self.receive_end_of_query()
609
+ self.receive_end_of_insert_query()
512
610
  return rv
513
611
 
514
612
  def receive_sample_block(self):
@@ -562,8 +660,15 @@ class Client(object):
562
660
  self.connection.send_data(block)
563
661
  inserted_rows += block.num_rows
564
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
+
565
667
  # Empty block means end of data.
566
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
+
567
672
  return inserted_rows
568
673
 
569
674
  def receive_end_of_query(self):
@@ -573,6 +678,9 @@ class Client(object):
573
678
  if packet.type == ServerPacketTypes.END_OF_STREAM:
574
679
  break
575
680
 
681
+ elif packet.type == ServerPacketTypes.PROGRESS:
682
+ self.last_query.store_progress(packet.progress)
683
+
576
684
  elif packet.type == ServerPacketTypes.EXCEPTION:
577
685
  raise packet.exception
578
686
 
@@ -582,9 +690,69 @@ class Client(object):
582
690
  elif packet.type == ServerPacketTypes.TABLE_COLUMNS:
583
691
  pass
584
692
 
693
+ elif packet.type == ServerPacketTypes.PROFILE_EVENTS:
694
+ self.last_query.store_profile(packet.profile_info)
695
+
696
+ else:
697
+ message = self.connection.unexpected_packet_message(
698
+ 'Exception, EndOfStream, Progress, TableColumns, '
699
+ 'ProfileEvents or Log', packet.type
700
+ )
701
+ raise errors.UnexpectedPacketFromServerError(message)
702
+
703
+ def receive_end_of_insert_query(self):
704
+ while True:
705
+ packet = self.connection.receive_packet()
706
+
707
+ if packet.type == ServerPacketTypes.END_OF_STREAM:
708
+ break
709
+
710
+ elif packet.type == ServerPacketTypes.LOG:
711
+ log_block(packet.block)
712
+
713
+ elif packet.type == ServerPacketTypes.PROGRESS:
714
+ self.last_query.store_progress(packet.progress)
715
+
716
+ elif packet.type == ServerPacketTypes.EXCEPTION:
717
+ raise packet.exception
718
+
719
+ else:
720
+ message = self.connection.unexpected_packet_message(
721
+ 'EndOfStream, Log, Progress or Exception', packet.type
722
+ )
723
+ raise errors.UnexpectedPacketFromServerError(message)
724
+
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
732
+
733
+ while True:
734
+ packet = self.connection.receive_packet()
735
+
736
+ if packet.type == ServerPacketTypes.PROFILE_EVENTS:
737
+ self.last_query.store_profile(packet.profile_info)
738
+ break
739
+
740
+ elif packet.type == ServerPacketTypes.PROGRESS:
741
+ self.last_query.store_progress(packet.progress)
742
+
743
+ elif packet.type == ServerPacketTypes.LOG:
744
+ log_block(packet.block)
745
+
746
+ elif packet.type == ServerPacketTypes.EXCEPTION:
747
+ raise packet.exception
748
+
749
+ elif packet.type == ServerPacketTypes.TIMEZONE_UPDATE:
750
+ pass
751
+
585
752
  else:
586
753
  message = self.connection.unexpected_packet_message(
587
- 'Exception, EndOfStream or Log', packet.type
754
+ 'ProfileEvents, Progress, Log, Exception or '
755
+ 'TimezoneUpdate', packet.type
588
756
  )
589
757
  raise errors.UnexpectedPacketFromServerError(message)
590
758
 
@@ -595,6 +763,26 @@ class Client(object):
595
763
  return self.receive_result(with_column_types=with_column_types)
596
764
 
597
765
  def substitute_params(self, query, params, context):
766
+ """
767
+ Substitutes parameters into a provided query.
768
+
769
+ For example::
770
+
771
+ client = Client(...)
772
+
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
785
+
598
786
  if not isinstance(params, dict):
599
787
  raise ValueError('Parameters are expected in dict form')
600
788
 
@@ -612,86 +800,13 @@ class Client(object):
612
800
  clickhouses://[user:password]@localhost:9440/default
613
801
 
614
802
  Three URL schemes are supported:
615
- clickhouse:// creates a normal TCP socket connection
616
- clickhouses:// creates a SSL wrapped TCP socket connection
803
+
804
+ * clickhouse:// creates a normal TCP socket connection
805
+ * clickhouses:// creates a SSL wrapped TCP socket connection
617
806
 
618
807
  Any additional querystring arguments will be passed along to
619
808
  the Connection class's initializer.
620
809
  """
621
- url = urlparse(url)
622
-
623
- settings = {}
624
- kwargs = {}
625
-
626
- host = url.hostname
627
-
628
- if url.port is not None:
629
- kwargs['port'] = url.port
630
-
631
- path = url.path.replace('/', '', 1)
632
- if path:
633
- kwargs['database'] = path
634
-
635
- if url.username is not None:
636
- kwargs['user'] = unquote(url.username)
637
-
638
- if url.password is not None:
639
- kwargs['password'] = unquote(url.password)
640
-
641
- if url.scheme == 'clickhouses':
642
- kwargs['secure'] = True
643
-
644
- compression_algs = {'lz4', 'lz4hc', 'zstd'}
645
- timeouts = {
646
- 'connect_timeout',
647
- 'send_receive_timeout',
648
- 'sync_request_timeout'
649
- }
650
-
651
- for name, value in parse_qs(url.query).items():
652
- if not value or not len(value):
653
- continue
654
-
655
- value = value[0]
656
-
657
- if name == 'compression':
658
- value = value.lower()
659
- if value in compression_algs:
660
- kwargs[name] = value
661
- else:
662
- kwargs[name] = asbool(value)
663
-
664
- elif name == 'secure':
665
- kwargs[name] = asbool(value)
666
-
667
- elif name == 'use_numpy':
668
- settings[name] = asbool(value)
669
-
670
- elif name == 'client_name':
671
- kwargs[name] = value
672
-
673
- elif name in timeouts:
674
- kwargs[name] = float(value)
675
-
676
- elif name == 'compress_block_size':
677
- kwargs[name] = int(value)
678
-
679
- elif name == 'settings_is_important':
680
- kwargs[name] = asbool(value)
681
-
682
- # ssl
683
- elif name == 'verify':
684
- kwargs[name] = asbool(value)
685
- elif name == 'ssl_version':
686
- kwargs[name] = getattr(ssl, value)
687
- elif name in ['ca_certs', 'ciphers', 'keyfile', 'certfile']:
688
- kwargs[name] = value
689
- elif name == 'alt_hosts':
690
- kwargs['alt_hosts'] = value
691
- else:
692
- settings[name] = value
693
-
694
- if settings:
695
- kwargs['settings'] = settings
810
+ host, kwargs = parse_url(url)
696
811
 
697
812
  return cls(host, **kwargs)
@@ -1,5 +1,6 @@
1
1
  import socket
2
2
  import getpass
3
+ from time import time
3
4
 
4
5
  from . import defines
5
6
  from . import errors
@@ -27,14 +28,13 @@ class ClientInfo(object):
27
28
  client_version_major = defines.CLIENT_VERSION_MAJOR
28
29
  client_version_minor = defines.CLIENT_VERSION_MINOR
29
30
  client_version_patch = defines.CLIENT_VERSION_PATCH
30
- client_revision = defines.CLIENT_REVISION
31
31
  interface = Interface.TCP
32
32
 
33
33
  initial_user = ''
34
34
  initial_query_id = ''
35
35
  initial_address = '0.0.0.0:0'
36
36
 
37
- def __init__(self, client_name, context):
37
+ def __init__(self, client_name, context, client_revision):
38
38
  self.query_kind = ClientInfo.QueryKind.NO_QUERY
39
39
 
40
40
  try:
@@ -43,6 +43,7 @@ class ClientInfo(object):
43
43
  self.os_user = ''
44
44
  self.client_hostname = socket.gethostname()
45
45
  self.client_name = client_name
46
+ self.client_revision = client_revision
46
47
 
47
48
  self.client_trace_context = OpenTelemetryTraceContext(
48
49
  context.client_settings['opentelemetry_traceparent'],
@@ -50,6 +51,8 @@ class ClientInfo(object):
50
51
  )
51
52
 
52
53
  self.quota_key = context.client_settings['quota_key']
54
+ self.distributed_depth = 0
55
+ self.initial_query_start_time_microseconds = int(time() * 1000000)
53
56
 
54
57
  super(ClientInfo, self).__init__()
55
58
 
@@ -71,6 +74,14 @@ class ClientInfo(object):
71
74
  write_binary_str(self.initial_query_id, fout)
72
75
  write_binary_str(self.initial_address, fout)
73
76
 
77
+ if (
78
+ revision >=
79
+ defines.DBMS_MIN_PROTOCOL_VERSION_WITH_INITIAL_QUERY_START_TIME
80
+ ):
81
+ write_binary_uint64(
82
+ self.initial_query_start_time_microseconds, fout
83
+ )
84
+
74
85
  write_binary_uint8(self.interface, fout)
75
86
 
76
87
  write_binary_str(self.os_user, fout)
@@ -83,6 +94,10 @@ class ClientInfo(object):
83
94
  if revision >= defines.DBMS_MIN_REVISION_WITH_QUOTA_KEY_IN_CLIENT_INFO:
84
95
  write_binary_str(self.quota_key, fout)
85
96
 
97
+ if revision >= \
98
+ defines.DBMS_MIN_PROTOCOL_VERSION_WITH_DISTRIBUTED_DEPTH:
99
+ write_varint(self.distributed_depth, fout)
100
+
86
101
  if revision >= defines.DBMS_MIN_REVISION_WITH_VERSION_PATCH:
87
102
  write_varint(self.client_version_patch, fout)
88
103
 
@@ -97,3 +112,8 @@ class ClientInfo(object):
97
112
  else:
98
113
  # Don't have OpenTelemetry header.
99
114
  write_binary_uint8(0, fout)
115
+
116
+ if revision >= defines.DBMS_MIN_REVISION_WITH_PARALLEL_REPLICAS:
117
+ write_varint(0, fout) # collaborate_with_initiator
118
+ write_varint(0, fout) # count_participating_replicas
119
+ write_varint(0, fout) # number_of_current_replica
@@ -28,25 +28,31 @@ class ArrayColumn(Column):
28
28
  py_types = (list, tuple)
29
29
 
30
30
  def __init__(self, nested_column, **kwargs):
31
- self.size_column = UInt64Column()
31
+ self.init_kwargs = kwargs
32
+ self.size_column = UInt64Column(**kwargs)
32
33
  self.nested_column = nested_column
33
34
  self._write_depth_0_size = True
34
35
  super(ArrayColumn, self).__init__(**kwargs)
36
+ self.null_value = []
35
37
 
36
38
  def write_data(self, data, buf):
37
39
  # Column of Array(T) is stored in "compact" format and passed to server
38
40
  # wrapped into another Array without size of wrapper array.
39
- self.nested_column = ArrayColumn(self.nested_column)
41
+ self.nested_column = ArrayColumn(
42
+ self.nested_column, **self.init_kwargs
43
+ )
40
44
  self.nested_column.nullable = self.nullable
41
45
  self.nullable = False
42
46
  self._write_depth_0_size = False
43
47
  self._write(data, buf)
44
48
 
45
- def read_data(self, rows, buf):
46
- self.nested_column = ArrayColumn(self.nested_column)
49
+ def read_data(self, n_rows, buf):
50
+ self.nested_column = ArrayColumn(
51
+ self.nested_column, **self.init_kwargs
52
+ )
47
53
  self.nested_column.nullable = self.nullable
48
54
  self.nullable = False
49
- return self._read(rows, buf)[0]
55
+ return self._read(n_rows, buf)[0]
50
56
 
51
57
  def _write_sizes(self, value, buf):
52
58
  nulls_map = []
@@ -99,14 +105,19 @@ class ArrayColumn(Column):
99
105
  self.nested_column._write_nulls_map(value, buf)
100
106
 
101
107
  def _write(self, value, buf):
108
+ value = self.prepare_items(value)
102
109
  self._write_sizes(value, buf)
103
110
  self._write_nulls_data(value, buf)
104
111
  self._write_data(value, buf)
105
112
 
106
113
  def read_state_prefix(self, buf):
107
- return self.nested_column.read_state_prefix(buf)
114
+ super(ArrayColumn, self).read_state_prefix(buf)
115
+
116
+ self.nested_column.read_state_prefix(buf)
108
117
 
109
118
  def write_state_prefix(self, buf):
119
+ super(ArrayColumn, self).write_state_prefix(buf)
120
+
110
121
  self.nested_column.write_state_prefix(buf)
111
122
 
112
123
  def _read(self, size, buf):
@@ -145,6 +156,6 @@ class ArrayColumn(Column):
145
156
  return tuple(data)
146
157
 
147
158
 
148
- def create_array_column(spec, column_by_spec_getter):
159
+ def create_array_column(spec, column_by_spec_getter, column_options):
149
160
  inner = spec[6:-1]
150
- return ArrayColumn(column_by_spec_getter(inner))
161
+ return ArrayColumn(column_by_spec_getter(inner), **column_options)