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.
- {e6data_python_connector-2.3.10rc7/e6data_python_connector.egg-info → e6data_python_connector-2.3.11}/PKG-INFO +57 -2
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/README.md +56 -1
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_manager.py +93 -9
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/datainputstream.py +31 -19
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6data_grpc.py +86 -25
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6x_vector/ttypes.py +26 -2
- e6data_python_connector-2.3.11/e6data_python_connector/server/e6x_engine_pb2.py +207 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/e6x_engine_pb2.pyi +560 -276
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +807 -282
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11/e6data_python_connector.egg-info}/PKG-INFO +57 -2
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/SOURCES.txt +4 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/top_level.txt +1 -0
- e6data_python_connector-2.3.11/gen-py/__init__.py +0 -0
- e6data_python_connector-2.3.11/gen-py/e6x_vector/__init__.py +1 -0
- e6data_python_connector-2.3.11/gen-py/e6x_vector/constants.py +15 -0
- e6data_python_connector-2.3.11/gen-py/e6x_vector/ttypes.py +1875 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/setup.py +1 -1
- e6data_python_connector-2.3.10rc7/e6data_python_connector/server/e6x_engine_pb2.py +0 -141
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/LICENSE +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/MANIFEST.in +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/common.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/connection_pool.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/constants.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/date_time_utils.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/dialect.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/strategy.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/pyproject.toml +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/setup.cfg +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/__init__.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_binary.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/analyze_fields.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/debug_binary.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/final_test.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/move_tests.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/quick_test.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_38_nines.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_connection_pool.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_connection_pool_e2e.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_fix.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_known_case.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_mock_server.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_pool_concurrency_simple.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_pool_threading_multiprocessing.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_simple_connection_pool.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/tests.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/tests_grpc.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-2.3.10rc7 → e6data_python_connector-2.3.11}/test/validate_implementation.py +0 -0
- {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.
|
|
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
|
-

|
|
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
|
-

|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
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
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
116
|
-
|
|
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)
|