e6data-python-connector 2.3.10rc7__tar.gz → 2.3.11__tar.gz

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 (88) hide show
  1. {e6data_python_connector-2.3.10rc7/e6data_python_connector.egg-info → e6data_python_connector-2.3.11}/PKG-INFO +57 -2
  2. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/README.md +56 -1
  3. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_manager.py +93 -9
  4. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/datainputstream.py +31 -19
  5. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6data_grpc.py +86 -25
  6. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6x_vector/ttypes.py +26 -2
  7. e6data_python_connector-2.3.11/e6data_python_connector/server/e6x_engine_pb2.py +207 -0
  8. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/e6x_engine_pb2.pyi +560 -276
  9. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +807 -282
  10. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11/e6data_python_connector.egg-info}/PKG-INFO +57 -2
  11. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/SOURCES.txt +4 -0
  12. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/top_level.txt +1 -0
  13. e6data_python_connector-2.3.11/gen-py/__init__.py +0 -0
  14. e6data_python_connector-2.3.11/gen-py/e6x_vector/__init__.py +1 -0
  15. e6data_python_connector-2.3.11/gen-py/e6x_vector/constants.py +15 -0
  16. e6data_python_connector-2.3.11/gen-py/e6x_vector/ttypes.py +1875 -0
  17. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/setup.py +1 -1
  18. e6data_python_connector-2.3.10rc7/e6data_python_connector/server/e6x_engine_pb2.py +0 -141
  19. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/LICENSE +0 -0
  20. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/MANIFEST.in +0 -0
  21. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/__init__.py +0 -0
  22. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_server/__init__.py +0 -0
  23. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
  24. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
  25. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/common.py +0 -0
  26. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/connection_pool.py +0 -0
  27. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/constants.py +0 -0
  28. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/date_time_utils.py +0 -0
  29. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/dialect.py +0 -0
  30. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6x_vector/__init__.py +0 -0
  31. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6x_vector/constants.py +0 -0
  32. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/exceptions.py +0 -0
  33. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/QueryEngineService.py +0 -0
  34. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/__init__.py +0 -0
  35. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/constants.py +0 -0
  36. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/ttypes.py +0 -0
  37. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/strategy.py +0 -0
  38. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/typeId.py +0 -0
  39. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
  40. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/entry_points.txt +0 -0
  41. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/requires.txt +0 -0
  42. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/pyproject.toml +0 -0
  43. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/setup.cfg +0 -0
  44. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/__init__.py +0 -0
  45. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_38_nines.py +0 -0
  46. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_all_cases.py +0 -0
  47. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_binary.py +0 -0
  48. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_correct_value.py +0 -0
  49. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_fields.py +0 -0
  50. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/check_decimal_errors.py +0 -0
  51. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/cleanup_test_files.py +0 -0
  52. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/debug_38_nines.py +0 -0
  53. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/debug_binary.py +0 -0
  54. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/final_test.py +0 -0
  55. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/move_tests.py +0 -0
  56. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/quick_test.py +0 -0
  57. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_38_nines.py +0 -0
  58. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_all_decimal128_cases.py +0 -0
  59. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_cluster_manager_efficiency.py +0 -0
  60. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_cluster_manager_none_strategy.py +0 -0
  61. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_cluster_manager_strategy.py +0 -0
  62. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_comprehensive.py +0 -0
  63. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_connection_pool.py +0 -0
  64. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_connection_pool_e2e.py +0 -0
  65. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_current_implementation.py +0 -0
  66. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_decimal128_binary_parsing.py +0 -0
  67. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_decimal128_parsing.py +0 -0
  68. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_fix.py +0 -0
  69. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_improved_parsing.py +0 -0
  70. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_known_case.py +0 -0
  71. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_manual_analysis.py +0 -0
  72. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_mock_server.py +0 -0
  73. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_multiprocessing_fix.py +0 -0
  74. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_new_implementation.py +0 -0
  75. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_pool_concurrency_simple.py +0 -0
  76. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_pool_threading_multiprocessing.py +0 -0
  77. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_simple_connection_pool.py +0 -0
  78. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_specific_binary.py +0 -0
  79. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy.py +0 -0
  80. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy_logic.py +0 -0
  81. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy_persistence_fix.py +0 -0
  82. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy_sharing_fix.py +0 -0
  83. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_user_binary_value.py +0 -0
  84. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/tests.py +0 -0
  85. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/tests_grpc.py +0 -0
  86. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/validate_decimal128.py +0 -0
  87. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/validate_implementation.py +0 -0
  88. {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/verify_decimal_fix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: e6data-python-connector
3
- Version: 2.3.10rc7
3
+ Version: 2.3.11
4
4
  Summary: Client for the e6data distributed SQL Engine.
5
5
  Home-page: https://github.com/e6x-labs/e6data-python-connector
6
6
  Author: e6data, Inc.
@@ -39,7 +39,7 @@ Dynamic: summary
39
39
 
40
40
  # e6data Python Connector
41
41
 
42
- ![version](https://img.shields.io/badge/version-2.3.10rc7-blue.svg)
42
+ ![version](https://img.shields.io/badge/version-2.3.11-blue.svg)
43
43
 
44
44
  ## Introduction
45
45
 
@@ -124,6 +124,7 @@ The `Connection` class supports the following parameters:
124
124
  | `secure` | bool | No | False | Enable SSL/TLS for secure connections |
125
125
  | `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
126
126
  | `grpc_options` | dict | No | None | Additional gRPC configuration options |
127
+ | `debug` | bool | No | False | Enable debug logging for troubleshooting |
127
128
 
128
129
  #### Secure Connection Example
129
130
 
@@ -637,4 +638,58 @@ conn.close()
637
638
  - Graceful connection recovery and retry logic
638
639
  - Blue-green deployment support with automatic failover
639
640
 
641
+ ## Debugging and Troubleshooting
642
+
643
+ ### Enable Debug Mode
644
+
645
+ Enable comprehensive debugging to troubleshoot connection and query issues:
646
+
647
+ ```python
648
+ from e6data_python_connector import Connection
649
+
650
+ conn = Connection(
651
+ host=host,
652
+ port=port,
653
+ username=username,
654
+ password=password,
655
+ database=database,
656
+ debug=True # Enable debug logging
657
+ )
658
+ ```
659
+
660
+ When `debug=True`, the following features are enabled:
661
+ - Python logging at DEBUG level for all operations
662
+ - Blue-green strategy transition logging
663
+ - Connection lifecycle logging
664
+ - Query execution detailed logging
665
+
666
+ ### gRPC Network Tracing
667
+
668
+ For low-level gRPC network debugging (HTTP/2 frames, TCP events), set environment variables **before** running your Python script:
669
+
670
+ ```bash
671
+ # Enable gRPC network tracing
672
+ export GRPC_VERBOSITY=DEBUG
673
+ export GRPC_TRACE=client_channel,http2
674
+
675
+ # For comprehensive tracing
676
+ export GRPC_TRACE=api,call_error,channel,client_channel,connectivity_state,http,http2_stream,tcp,transport_security
677
+
678
+ # Run your script
679
+ python your_script.py
680
+ ```
681
+
682
+ **Note**: These environment variables must be set before Python starts, as the gRPC C++ core reads them at module import time.
683
+
684
+ ### Common Issues and Solutions
685
+
686
+ | Issue | Solution |
687
+ |-------|----------|
688
+ | Connection timeout | Check network connectivity, firewall rules, and ensure port 80/443 is open |
689
+ | Authentication failure | Verify username (email) and access token are correct |
690
+ | 503 Service Unavailable | Cluster may be suspended; enable `auto_resume=True` |
691
+ | 456 Strategy Error | Automatic blue-green failover will handle this |
692
+ | Memory issues with large results | Use `fetchall_buffer()` instead of `fetchall()` |
693
+ | gRPC message size errors | Configure `grpc_options` with appropriate message size limits |
694
+
640
695
  See [TECH_DOC.md](TECH_DOC.md) for detailed technical documentation.
@@ -1,6 +1,6 @@
1
1
  # e6data Python Connector
2
2
 
3
- ![version](https://img.shields.io/badge/version-2.3.10rc7-blue.svg)
3
+ ![version](https://img.shields.io/badge/version-2.3.11-blue.svg)
4
4
 
5
5
  ## Introduction
6
6
 
@@ -85,6 +85,7 @@ The `Connection` class supports the following parameters:
85
85
  | `secure` | bool | No | False | Enable SSL/TLS for secure connections |
86
86
  | `auto_resume` | bool | No | True | Automatically resume cluster if suspended |
87
87
  | `grpc_options` | dict | No | None | Additional gRPC configuration options |
88
+ | `debug` | bool | No | False | Enable debug logging for troubleshooting |
88
89
 
89
90
  #### Secure Connection Example
90
91
 
@@ -598,4 +599,58 @@ conn.close()
598
599
  - Graceful connection recovery and retry logic
599
600
  - Blue-green deployment support with automatic failover
600
601
 
602
+ ## Debugging and Troubleshooting
603
+
604
+ ### Enable Debug Mode
605
+
606
+ Enable comprehensive debugging to troubleshoot connection and query issues:
607
+
608
+ ```python
609
+ from e6data_python_connector import Connection
610
+
611
+ conn = Connection(
612
+ host=host,
613
+ port=port,
614
+ username=username,
615
+ password=password,
616
+ database=database,
617
+ debug=True # Enable debug logging
618
+ )
619
+ ```
620
+
621
+ When `debug=True`, the following features are enabled:
622
+ - Python logging at DEBUG level for all operations
623
+ - Blue-green strategy transition logging
624
+ - Connection lifecycle logging
625
+ - Query execution detailed logging
626
+
627
+ ### gRPC Network Tracing
628
+
629
+ For low-level gRPC network debugging (HTTP/2 frames, TCP events), set environment variables **before** running your Python script:
630
+
631
+ ```bash
632
+ # Enable gRPC network tracing
633
+ export GRPC_VERBOSITY=DEBUG
634
+ export GRPC_TRACE=client_channel,http2
635
+
636
+ # For comprehensive tracing
637
+ export GRPC_TRACE=api,call_error,channel,client_channel,connectivity_state,http,http2_stream,tcp,transport_security
638
+
639
+ # Run your script
640
+ python your_script.py
641
+ ```
642
+
643
+ **Note**: These environment variables must be set before Python starts, as the gRPC C++ core reads them at module import time.
644
+
645
+ ### Common Issues and Solutions
646
+
647
+ | Issue | Solution |
648
+ |-------|----------|
649
+ | Connection timeout | Check network connectivity, firewall rules, and ensure port 80/443 is open |
650
+ | Authentication failure | Verify username (email) and access token are correct |
651
+ | 503 Service Unavailable | Cluster may be suspended; enable `auto_resume=True` |
652
+ | 456 Strategy Error | Automatic blue-green failover will handle this |
653
+ | Memory issues with large results | Use `fetchall_buffer()` instead of `fetchall()` |
654
+ | gRPC message size errors | Configure `grpc_options` with appropriate message size limits |
655
+
601
656
  See [TECH_DOC.md](TECH_DOC.md) for detailed technical documentation.
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import threading
2
3
  import time
3
4
  import e6data_python_connector.cluster_server.cluster_pb2 as cluster_pb2
@@ -6,6 +7,8 @@ import grpc
6
7
  from grpc._channel import _InactiveRpcError
7
8
  import multiprocessing
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
  from e6data_python_connector.strategy import _get_active_strategy, _set_active_strategy, _set_pending_strategy, \
10
13
  _get_grpc_header as _get_strategy_header
11
14
 
@@ -137,7 +140,7 @@ class ClusterManager:
137
140
  """
138
141
 
139
142
  def __init__(self, host: str, port: int, user: str, password: str, secure_channel: bool = False, timeout=60 * 5,
140
- cluster_uuid=None, grpc_options=None):
143
+ cluster_uuid=None, grpc_options=None, debug=False):
141
144
  """
142
145
  Initializes a new instance of the ClusterManager class.
143
146
 
@@ -152,6 +155,7 @@ class ClusterManager:
152
155
  defaults to 5 minutes.
153
156
  cluster_uuid (str, optional): The unique identifier for the target cluster;
154
157
  defaults to None.
158
+ debug (bool, optional): Enable debug logging; defaults to False.
155
159
  """
156
160
 
157
161
  self._host = host
@@ -164,6 +168,7 @@ class ClusterManager:
164
168
  self._grpc_options = grpc_options
165
169
  if grpc_options is None:
166
170
  self._grpc_options = dict()
171
+ self._debug = debug
167
172
 
168
173
  @property
169
174
  def _get_connection(self):
@@ -191,20 +196,22 @@ class ClusterManager:
191
196
  def _try_cluster_request(self, request_type, payload=None):
192
197
  """
193
198
  Execute a cluster request with strategy fallback for 456 errors.
194
-
199
+
195
200
  For efficiency:
196
201
  - If we have an active strategy, use it first
197
202
  - Only try authentication sequence (blue -> green) if no active strategy
198
203
  - On 456 error, switch to alternative strategy and update active strategy
199
-
204
+
200
205
  Args:
201
206
  request_type: Type of request ('status' or 'resume')
202
207
  payload: Request payload (optional, will be created if not provided)
203
-
208
+
204
209
  Returns:
205
210
  The response from the successful request
206
211
  """
207
212
  current_strategy = _get_active_strategy()
213
+ if self._debug:
214
+ logger.info(f"Attempting {request_type} request with current strategy: {current_strategy}")
208
215
 
209
216
  # Create payload if not provided
210
217
  if payload is None:
@@ -222,6 +229,8 @@ class ClusterManager:
222
229
  # If we have an active strategy, use it first
223
230
  if current_strategy is not None:
224
231
  try:
232
+ if self._debug:
233
+ logger.info(f"Executing {request_type} with strategy '{current_strategy}'")
225
234
  if request_type == "status":
226
235
  response = self._get_connection.status(
227
236
  payload,
@@ -239,16 +248,24 @@ class ClusterManager:
239
248
  if hasattr(response, 'new_strategy') and response.new_strategy:
240
249
  new_strategy = response.new_strategy.lower()
241
250
  if new_strategy != current_strategy:
251
+ if self._debug:
252
+ logger.info(f"Server indicated strategy transition from '{current_strategy}' to '{new_strategy}'")
242
253
  _set_pending_strategy(new_strategy)
243
254
 
255
+ if self._debug:
256
+ logger.info(f"{request_type} request successful with strategy '{current_strategy}'")
244
257
  return response
245
258
 
246
259
  except _InactiveRpcError as e:
247
260
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
248
261
  # 456 error - switch to alternative strategy
249
262
  alternative_strategy = 'green' if current_strategy == 'blue' else 'blue'
263
+ if self._debug:
264
+ logger.info(f"Received 456 error with strategy '{current_strategy}', switching to '{alternative_strategy}'")
250
265
 
251
266
  try:
267
+ if self._debug:
268
+ logger.info(f"Retrying {request_type} with alternative strategy '{alternative_strategy}'")
252
269
  if request_type == "status":
253
270
  response = self._get_connection.status(
254
271
  payload,
@@ -261,27 +278,39 @@ class ClusterManager:
261
278
  )
262
279
 
263
280
  # Update active strategy since the alternative worked
281
+ if self._debug:
282
+ logger.info(f"Successfully switched to strategy '{alternative_strategy}'")
264
283
  _set_active_strategy(alternative_strategy)
265
284
 
266
285
  # Check for new strategy in response
267
286
  if hasattr(response, 'new_strategy') and response.new_strategy:
268
287
  new_strategy = response.new_strategy.lower()
269
288
  if new_strategy != alternative_strategy:
289
+ if self._debug:
290
+ logger.info(f"Server indicated future strategy transition to '{new_strategy}'")
270
291
  _set_pending_strategy(new_strategy)
271
292
 
272
293
  return response
273
294
 
274
295
  except _InactiveRpcError as e2:
296
+ if self._debug:
297
+ logger.error(f"Failed with alternative strategy '{alternative_strategy}': {e2}")
275
298
  raise e # Raise the original error
276
299
  else:
277
300
  # Non-456 error - don't retry
301
+ if self._debug:
302
+ logger.info(f"Non-456 error received, not retrying: {e}")
278
303
  raise e
279
304
 
280
305
  # No active strategy - start with authentication logic (blue first, then green)
281
306
  strategies_to_try = ['blue', 'green']
307
+ if self._debug:
308
+ logger.info("No active strategy found, trying authentication sequence: blue -> green")
282
309
 
283
310
  for i, strategy in enumerate(strategies_to_try):
284
311
  try:
312
+ if self._debug:
313
+ logger.info(f"Trying {request_type} with strategy '{strategy}' (attempt {i+1}/{len(strategies_to_try)})")
285
314
 
286
315
  if request_type == "status":
287
316
  response = self._get_connection.status(
@@ -297,12 +326,16 @@ class ClusterManager:
297
326
  raise ValueError(f"Unknown request type: {request_type}")
298
327
 
299
328
  # Set the working strategy as active
329
+ if self._debug:
330
+ logger.info(f"Successfully authenticated with strategy '{strategy}'")
300
331
  _set_active_strategy(strategy)
301
332
 
302
333
  # Check for new strategy in response
303
334
  if hasattr(response, 'new_strategy') and response.new_strategy:
304
335
  new_strategy = response.new_strategy.lower()
305
336
  if new_strategy != strategy:
337
+ if self._debug:
338
+ logger.info(f"Server indicated strategy transition to '{new_strategy}'")
306
339
  _set_pending_strategy(new_strategy)
307
340
 
308
341
  return response
@@ -311,14 +344,22 @@ class ClusterManager:
311
344
  if e.code() == grpc.StatusCode.UNKNOWN and 'status: 456' in e.details():
312
345
  # 456 error - try next strategy
313
346
  if i < len(strategies_to_try) - 1:
347
+ if self._debug:
348
+ logger.info(f"Strategy '{strategy}' returned 456 error, trying next strategy")
314
349
  continue
315
350
  else:
351
+ if self._debug:
352
+ logger.error(f"All strategies failed with 456 errors")
316
353
  raise e
317
354
  else:
318
355
  # Non-456 error - don't retry
356
+ if self._debug:
357
+ logger.error(f"Strategy '{strategy}' failed with non-456 error: {e}")
319
358
  raise e
320
359
 
321
360
  # If we get here, all strategies failed
361
+ if self._debug:
362
+ logger.error("All authentication strategies exhausted")
322
363
  raise e
323
364
 
324
365
  def _check_cluster_status(self):
@@ -326,8 +367,12 @@ class ClusterManager:
326
367
  try:
327
368
  # Use the unified strategy-aware request method
328
369
  response = self._try_cluster_request("status")
370
+ if self._debug:
371
+ logger.info(f"Cluster status check returned: {response.status}")
329
372
  yield response.status
330
- except _InactiveRpcError:
373
+ except _InactiveRpcError as e:
374
+ if self._debug:
375
+ logger.warning(f"Status check failed with error: {e}")
331
376
  yield None
332
377
 
333
378
  def resume(self) -> bool:
@@ -357,38 +402,77 @@ class ClusterManager:
357
402
  - The operation is subject to the `_timeout` threshold;
358
403
  if the timeout expires, the method returns False.
359
404
  """
405
+ if self._debug:
406
+ logger.info(f"Starting auto-resume for cluster {self.cluster_uuid} at {self._host}:{self._port}")
360
407
 
361
408
  with status_lock as lock:
362
409
  if lock.is_active:
410
+ if self._debug:
411
+ logger.info("Lock is already active, cluster appears to be running")
363
412
  return True
364
413
 
365
414
  # Retrieve the current cluster status with strategy header
415
+ if self._debug:
416
+ logger.info("Checking current cluster status")
366
417
  try:
367
418
  current_status = self._try_cluster_request("status")
368
- except _InactiveRpcError:
419
+ if self._debug:
420
+ logger.info(f"Current cluster status: {current_status.status}")
421
+ except _InactiveRpcError as e:
422
+ if self._debug:
423
+ logger.error(f"Failed to get cluster status: {e}")
369
424
  return False
425
+
370
426
  if current_status.status == 'suspended':
371
427
  # Send the resume request with strategy header
428
+ if self._debug:
429
+ logger.info("Cluster is suspended, sending resume request")
372
430
  try:
373
431
  response = self._try_cluster_request("resume")
374
- except _InactiveRpcError:
432
+ if self._debug:
433
+ logger.info("Resume request sent successfully")
434
+ except _InactiveRpcError as e:
435
+ if self._debug:
436
+ logger.error(f"Failed to send resume request: {e}")
375
437
  return False
376
438
  elif current_status.status == 'active':
439
+ if self._debug:
440
+ logger.info("Cluster is already active, no action needed")
377
441
  return True
378
442
  elif current_status.status != 'resuming':
379
443
  """
380
- If cluster cannot be resumed due to its current state,
444
+ If cluster cannot be resumed due to its current state,
381
445
  or already in a process of resuming, terminate the operation.
382
446
  """
447
+ if self._debug:
448
+ logger.warning(f"Cluster is in state '{current_status.status}' which cannot be resumed")
383
449
  return False
384
450
 
451
+ if self._debug:
452
+ logger.info("Monitoring cluster status until it becomes active...")
453
+ check_count = 0
385
454
  for status in self._check_cluster_status():
455
+ check_count += 1
386
456
  if status == 'active':
457
+ if self._debug:
458
+ logger.info(f"Cluster became active after {check_count} status checks")
387
459
  return True
388
- elif status == 'failed' or time.time() > self._timeout:
460
+ elif status == 'failed':
461
+ if self._debug:
462
+ logger.error(f"Cluster failed to resume after {check_count} status checks")
463
+ return False
464
+ elif time.time() > self._timeout:
465
+ if self._debug:
466
+ logger.error(f"Resume operation timed out after {check_count} status checks")
389
467
  return False
468
+
469
+ if self._debug:
470
+ logger.info(f"Status check #{check_count}: {status}, waiting 5 seconds before next check")
390
471
  # Wait for 5 seconds before the next status check
391
472
  time.sleep(5)
473
+
474
+ if self._debug:
475
+ logger.warning("Resume operation completed without reaching active state")
392
476
  return False
393
477
 
394
478
  def suspend(self):
@@ -26,16 +26,18 @@ except ImportError:
26
26
  _logger = logging.getLogger(__name__)
27
27
 
28
28
 
29
- def _binary_to_decimal128(binary_data):
29
+ def _binary_to_decimal128(binary_data, scale=None):
30
30
  """
31
31
  Convert binary data to Decimal128.
32
-
32
+
33
33
  The binary data represents a 128-bit decimal number in IEEE 754-2008 Decimal128 format.
34
34
  Based on the Java implementation from e6data's JDBC driver.
35
-
35
+
36
36
  Args:
37
37
  binary_data (bytes): Binary representation of Decimal128
38
-
38
+ scale (int, optional): Scale parameter for decimal precision. If None,
39
+ attempts to decode from IEEE format or defaults to 0.
40
+
39
41
  Returns:
40
42
  Decimal: Python Decimal object
41
43
  """
@@ -59,7 +61,7 @@ def _binary_to_decimal128(binary_data):
59
61
 
60
62
  # Handle IEEE 754-2008 Decimal128 binary format
61
63
  if len(binary_data) == 16: # Decimal128 should be exactly 16 bytes
62
- return _decode_decimal128_binary_java_style(binary_data)
64
+ return _decode_decimal128_binary_java_style(binary_data, scale)
63
65
  else:
64
66
  _logger.warning(f"Invalid Decimal128 binary length: {len(binary_data)} bytes, expected 16")
65
67
  return Decimal('0')
@@ -73,16 +75,17 @@ def _binary_to_decimal128(binary_data):
73
75
  return Decimal('0')
74
76
 
75
77
 
76
- def _decode_decimal128_binary_java_style(binary_data):
78
+ def _decode_decimal128_binary_java_style(binary_data, scale=None):
77
79
  """
78
80
  Decode IEEE 754-2008 Decimal128 binary format following Java implementation.
79
-
81
+
80
82
  Based on the Java implementation from e6data's JDBC driver getFieldDataFromChunk method.
81
83
  This method follows the same logic as the Java BigDecimal creation from ByteBuffer.
82
-
84
+
83
85
  Args:
84
86
  binary_data (bytes): 16-byte binary representation
85
-
87
+ scale (int, optional): Scale parameter for decimal precision
88
+
86
89
  Returns:
87
90
  Decimal: Python Decimal object
88
91
  """
@@ -103,17 +106,21 @@ def _decode_decimal128_binary_java_style(binary_data):
103
106
  if big_int_value == 0:
104
107
  return Decimal('0')
105
108
 
106
- # The Java code creates BigDecimal from BigInteger with scale 0
107
- # This means we treat the integer value as the unscaled value
108
- # However, for Decimal128, we need to handle the scaling properly
109
-
110
- # Try to create decimal directly from the integer value
111
- decimal_value = Decimal(big_int_value)
109
+ # The Java code creates BigDecimal from BigInteger with the provided scale
110
+ # If scale is provided, use it to create the properly scaled decimal
111
+ if scale is not None:
112
+ # Create decimal with the specified scale (like Java's BigDecimal constructor)
113
+ # This treats big_int_value as the unscaled value
114
+ decimal_value = Decimal(big_int_value) / (Decimal(10) ** scale)
115
+ else:
116
+ # Fallback: try to create decimal directly from the integer value
117
+ decimal_value = Decimal(big_int_value)
112
118
 
113
119
  # Check if this produces a reasonable decimal value
114
120
  # Decimal128 should represent normal decimal numbers
115
- if abs(decimal_value) < Decimal('1E-6143') or abs(decimal_value) > Decimal(
116
- '9.999999999999999999999999999999999E+6144'):
121
+ # Only check range if scale was not provided (backward compatibility)
122
+ if scale is None and (abs(decimal_value) < Decimal('1E-6143') or abs(decimal_value) > Decimal(
123
+ '9.999999999999999999999999999999999E+6144')):
117
124
  # Value is outside normal Decimal128 range, try alternative interpretation
118
125
  return _decode_decimal128_alternative(binary_data)
119
126
 
@@ -624,10 +631,12 @@ def get_column_from_chunk(vector: Vector) -> list:
624
631
  if vector.isConstantVector:
625
632
  # For constant vectors, get the binary data and convert it once
626
633
  binary_data = vector.data.numericDecimal128ConstantData.data
634
+ # Get scale with backward compatibility for older engines
635
+ scale = getattr(vector.data.numericDecimal128ConstantData, 'scale', None)
627
636
 
628
637
  # Convert binary data to BigDecimal equivalent
629
638
  if binary_data:
630
- decimal_value = _binary_to_decimal128(binary_data)
639
+ decimal_value = _binary_to_decimal128(binary_data, scale)
631
640
  else:
632
641
  decimal_value = Decimal('0')
633
642
 
@@ -639,13 +648,16 @@ def get_column_from_chunk(vector: Vector) -> list:
639
648
  value_array.append(decimal_value)
640
649
  else:
641
650
  # For non-constant vectors, process each row individually
651
+ # Get scale from decimal128Data (with backward compatibility)
652
+ scale = getattr(vector.data.decimal128Data, 'scale', None)
653
+
642
654
  for row in range(vector.size):
643
655
  if get_null(vector, row):
644
656
  value_array.append(None)
645
657
  continue
646
658
  # Get binary data for this row
647
659
  binary_data = vector.data.decimal128Data.data[row]
648
- decimal_value = _binary_to_decimal128(binary_data)
660
+ decimal_value = _binary_to_decimal128(binary_data, scale)
649
661
  value_array.append(decimal_value)
650
662
  else:
651
663
  value_array.append(None)