e6data-python-connector 2.3.12rc32__tar.gz → 2.3.13rc1__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.12rc32/e6data_python_connector.egg-info → e6data_python_connector-2.3.13rc1}/PKG-INFO +6 -2
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/README.md +5 -1
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/cluster_manager.py +3 -60
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/common.py +44 -0
- e6data_python_connector-2.3.13rc1/e6data_python_connector/constants.py +56 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/datainputstream.py +46 -36
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/date_time_utils.py +95 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/e6data_grpc.py +14 -57
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1/e6data_python_connector.egg-info}/PKG-INFO +6 -2
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector.egg-info/SOURCES.txt +4 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/setup.py +1 -1
- e6data_python_connector-2.3.13rc1/test/benchmark_fastbinary_performance.py +303 -0
- e6data_python_connector-2.3.13rc1/test/test_code_optimizations.py +322 -0
- e6data_python_connector-2.3.13rc1/test/test_optimizations_integration.py +443 -0
- e6data_python_connector-2.3.13rc1/test/test_ssl_cert.py +548 -0
- e6data_python_connector-2.3.12rc32/e6data_python_connector/constants.py +0 -32
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/LICENSE +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/MANIFEST.in +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/dialect.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/strategy.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/e6data_python_connector.egg-info/top_level.txt +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/gen-py/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/gen-py/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/gen-py/e6x_vector/constants.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/gen-py/e6x_vector/ttypes.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/pyproject.toml +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/setup.cfg +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/__init__.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/analyze_binary.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/analyze_fields.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/debug_binary.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/final_test.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/move_tests.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/quick_test.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_38_nines.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_connection_pool_e2e.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_fix.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_known_case.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_mock_server.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_pool_concurrency_simple.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_pool_threading_multiprocessing.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_simple_connection_pool.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_strategy.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/tests.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/tests_grpc.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/test/validate_implementation.py +0 -0
- {e6data_python_connector-2.3.12rc32 → e6data_python_connector-2.3.13rc1}/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.13rc1
|
|
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
|
|
|
@@ -54,6 +54,10 @@ yum install python3-devel gcc-c++ -y
|
|
|
54
54
|
# Ubuntu/Debian dependencies
|
|
55
55
|
apt install python3-dev g++ -y
|
|
56
56
|
|
|
57
|
+
# Windows dependencies
|
|
58
|
+
# Install Visual C++ Build Tools from:
|
|
59
|
+
# https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
|
60
|
+
# Select the "Desktop Development with C++" option during installation.
|
|
57
61
|
|
|
58
62
|
# Pip dependencies
|
|
59
63
|
pip install wheel
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# e6data Python Connector
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
## Introduction
|
|
6
6
|
|
|
@@ -15,6 +15,10 @@ yum install python3-devel gcc-c++ -y
|
|
|
15
15
|
# Ubuntu/Debian dependencies
|
|
16
16
|
apt install python3-dev g++ -y
|
|
17
17
|
|
|
18
|
+
# Windows dependencies
|
|
19
|
+
# Install Visual C++ Build Tools from:
|
|
20
|
+
# https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
|
21
|
+
# Select the "Desktop Development with C++" option during installation.
|
|
18
22
|
|
|
19
23
|
# Pip dependencies
|
|
20
24
|
pip install wheel
|
|
@@ -9,27 +9,9 @@ import multiprocessing
|
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
|
+
from e6data_python_connector.common import get_ssl_credentials
|
|
12
13
|
from e6data_python_connector.strategy import _get_active_strategy, _set_active_strategy, _set_pending_strategy, \
|
|
13
|
-
_get_grpc_header
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _get_grpc_header(engine_ip=None, cluster=None, strategy=None):
|
|
17
|
-
"""
|
|
18
|
-
Generate gRPC metadata headers for the request.
|
|
19
|
-
|
|
20
|
-
This function creates a list of metadata headers to be used in gRPC requests.
|
|
21
|
-
It includes optional headers for the engine IP, cluster UUID, and deployment strategy.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
engine_ip (str, optional): The IP address of the engine. Defaults to None.
|
|
25
|
-
cluster (str, optional): The UUID of the cluster. Defaults to None.
|
|
26
|
-
strategy (str, optional): The deployment strategy (blue/green). Defaults to None.
|
|
27
|
-
|
|
28
|
-
Returns:
|
|
29
|
-
list: A list of tuples representing the gRPC metadata headers.
|
|
30
|
-
"""
|
|
31
|
-
# Use the strategy module's implementation
|
|
32
|
-
return _get_strategy_header(engine_ip=engine_ip, cluster=cluster, strategy=strategy)
|
|
14
|
+
_get_grpc_header
|
|
33
15
|
|
|
34
16
|
|
|
35
17
|
class _StatusLock:
|
|
@@ -173,45 +155,6 @@ class ClusterManager:
|
|
|
173
155
|
self._debug = debug
|
|
174
156
|
self._ssl_cert = ssl_cert
|
|
175
157
|
|
|
176
|
-
def _get_ssl_credentials(self):
|
|
177
|
-
"""
|
|
178
|
-
Get SSL credentials for secure gRPC channel.
|
|
179
|
-
|
|
180
|
-
Handles three scenarios:
|
|
181
|
-
1. ssl_cert is a string (file path): Read the PEM certificate from the file
|
|
182
|
-
2. ssl_cert is bytes: Use the certificate content directly
|
|
183
|
-
3. ssl_cert is None: Use system default CA bundle
|
|
184
|
-
|
|
185
|
-
Returns:
|
|
186
|
-
grpc.ChannelCredentials: SSL credentials for secure channel
|
|
187
|
-
|
|
188
|
-
Raises:
|
|
189
|
-
FileNotFoundError: If ssl_cert is a file path but the file doesn't exist
|
|
190
|
-
IOError: If ssl_cert file cannot be read
|
|
191
|
-
"""
|
|
192
|
-
if self._ssl_cert is None:
|
|
193
|
-
# Use system default CA bundle
|
|
194
|
-
return grpc.ssl_channel_credentials()
|
|
195
|
-
elif isinstance(self._ssl_cert, str):
|
|
196
|
-
# ssl_cert is a file path - read the certificate from file
|
|
197
|
-
try:
|
|
198
|
-
with open(self._ssl_cert, 'rb') as cert_file:
|
|
199
|
-
root_ca_cert = cert_file.read()
|
|
200
|
-
return grpc.ssl_channel_credentials(root_certificates=root_ca_cert)
|
|
201
|
-
except FileNotFoundError:
|
|
202
|
-
logger.error(f"SSL certificate file not found: {self._ssl_cert}")
|
|
203
|
-
raise
|
|
204
|
-
except IOError as e:
|
|
205
|
-
logger.error(f"Failed to read SSL certificate file {self._ssl_cert}: {e}")
|
|
206
|
-
raise
|
|
207
|
-
elif isinstance(self._ssl_cert, bytes):
|
|
208
|
-
# ssl_cert is certificate content as bytes
|
|
209
|
-
return grpc.ssl_channel_credentials(root_certificates=self._ssl_cert)
|
|
210
|
-
else:
|
|
211
|
-
# Invalid type - log warning and use system default
|
|
212
|
-
logger.warning(f"Invalid ssl_cert type: {type(self._ssl_cert)}. Using system default CA bundle.")
|
|
213
|
-
return grpc.ssl_channel_credentials()
|
|
214
|
-
|
|
215
158
|
@property
|
|
216
159
|
def _get_connection(self):
|
|
217
160
|
"""
|
|
@@ -226,7 +169,7 @@ class ClusterManager:
|
|
|
226
169
|
self._channel = grpc.secure_channel(
|
|
227
170
|
target='{}:{}'.format(self._host, self._port),
|
|
228
171
|
options=self._grpc_options,
|
|
229
|
-
credentials=self.
|
|
172
|
+
credentials=get_ssl_credentials(self._ssl_cert)
|
|
230
173
|
)
|
|
231
174
|
else:
|
|
232
175
|
self._channel = grpc.insecure_channel(
|
|
@@ -15,6 +15,7 @@ from builtins import int
|
|
|
15
15
|
from builtins import object
|
|
16
16
|
from builtins import str
|
|
17
17
|
|
|
18
|
+
import grpc
|
|
18
19
|
from future.utils import with_metaclass
|
|
19
20
|
from past.builtins import basestring
|
|
20
21
|
|
|
@@ -267,3 +268,46 @@ class UniversalSet(object):
|
|
|
267
268
|
|
|
268
269
|
def __contains__(self, item):
|
|
269
270
|
return True
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def get_ssl_credentials(ssl_cert):
|
|
274
|
+
"""
|
|
275
|
+
Get SSL credentials for secure gRPC channel.
|
|
276
|
+
|
|
277
|
+
Handles three scenarios:
|
|
278
|
+
1. ssl_cert is a string (file path): Read the PEM certificate from the file
|
|
279
|
+
2. ssl_cert is bytes: Use the certificate content directly
|
|
280
|
+
3. ssl_cert is None: Use system default CA bundle
|
|
281
|
+
|
|
282
|
+
Parameters:
|
|
283
|
+
ssl_cert (str or bytes or None): SSL certificate as file path, bytes, or None
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
grpc.ChannelCredentials: SSL credentials for gRPC channel
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
FileNotFoundError: If ssl_cert is a file path but the file doesn't exist
|
|
290
|
+
IOError: If ssl_cert file cannot be read
|
|
291
|
+
"""
|
|
292
|
+
if ssl_cert is None:
|
|
293
|
+
# Use system default CA bundle
|
|
294
|
+
return grpc.ssl_channel_credentials()
|
|
295
|
+
elif isinstance(ssl_cert, str):
|
|
296
|
+
# ssl_cert is a file path - read the certificate from file
|
|
297
|
+
try:
|
|
298
|
+
with open(ssl_cert, 'rb') as cert_file:
|
|
299
|
+
root_ca_cert = cert_file.read()
|
|
300
|
+
return grpc.ssl_channel_credentials(root_certificates=root_ca_cert)
|
|
301
|
+
except FileNotFoundError:
|
|
302
|
+
_logger.error("SSL certificate file not found: {}".format(ssl_cert))
|
|
303
|
+
raise
|
|
304
|
+
except IOError as e:
|
|
305
|
+
_logger.error("Failed to read SSL certificate file {}: {}".format(ssl_cert, e))
|
|
306
|
+
raise
|
|
307
|
+
elif isinstance(ssl_cert, bytes):
|
|
308
|
+
# ssl_cert is certificate content as bytes
|
|
309
|
+
return grpc.ssl_channel_credentials(root_certificates=ssl_cert)
|
|
310
|
+
else:
|
|
311
|
+
# Invalid type - fall back to system default CA bundle with warning
|
|
312
|
+
_logger.warning("Invalid ssl_cert type: {}. Using system default CA bundle.".format(type(ssl_cert)))
|
|
313
|
+
return grpc.ssl_channel_credentials()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import pytz
|
|
2
|
+
|
|
3
|
+
# Type definitions
|
|
4
|
+
PRIMITIVE_TYPES = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, 18, 19, 20, 21}
|
|
5
|
+
COMPLEX_TYPES = {10, 11, 12, 13, 14}
|
|
6
|
+
COLLECTION_TYPES = {10, 11}
|
|
7
|
+
TYPE_NAMES = {
|
|
8
|
+
0: "BOOLEAN",
|
|
9
|
+
1: "TINYINT",
|
|
10
|
+
2: "SMALLINT",
|
|
11
|
+
3: "INT",
|
|
12
|
+
4: "BIGINT",
|
|
13
|
+
5: "FLOAT",
|
|
14
|
+
6: "DOUBLE",
|
|
15
|
+
7: "STRING",
|
|
16
|
+
8: "TIMESTAMP",
|
|
17
|
+
9: "BINARY",
|
|
18
|
+
10: "ARRAY",
|
|
19
|
+
11: "MAP",
|
|
20
|
+
12: "STRUCT",
|
|
21
|
+
13: "UNIONTYPE",
|
|
22
|
+
15: "DECIMAL",
|
|
23
|
+
16: "NULL",
|
|
24
|
+
17: "DATE",
|
|
25
|
+
18: "VARCHAR",
|
|
26
|
+
19: "CHAR",
|
|
27
|
+
20: "INTERVAL_YEAR_MONTH",
|
|
28
|
+
21: "INTERVAL_DAY_TIME",
|
|
29
|
+
}
|
|
30
|
+
CHARACTER_MAXIMUM_LENGTH = "characterMaximumLength"
|
|
31
|
+
PRECISION = "precision"
|
|
32
|
+
SCALE = "scale"
|
|
33
|
+
ZONE = pytz.timezone('UTC')
|
|
34
|
+
|
|
35
|
+
# Retry and timeout constants
|
|
36
|
+
MAX_RETRY_ATTEMPTS = 5
|
|
37
|
+
RETRY_SLEEP_SECONDS = 0.2
|
|
38
|
+
STRATEGY_CACHE_TIMEOUT_SECONDS = 300 # 5 minutes
|
|
39
|
+
DEFAULT_GRPC_PREPARE_TIMEOUT_SECONDS = 600 # 10 minutes
|
|
40
|
+
DEFAULT_AUTO_RESUME_TIMEOUT_SECONDS = 300 # 5 minutes
|
|
41
|
+
CLUSTER_STATUS_CHECK_SLEEP_SECONDS = 5
|
|
42
|
+
LOCK_TIMEOUT_MS = 500
|
|
43
|
+
|
|
44
|
+
# Connection pool constants
|
|
45
|
+
POOL_GET_TIMEOUT_SECONDS = 0.1
|
|
46
|
+
POOL_RETRY_SLEEP_SECONDS = 0.1
|
|
47
|
+
|
|
48
|
+
# Blue-green deployment strategy constants
|
|
49
|
+
STRATEGY_BLUE = 'blue'
|
|
50
|
+
STRATEGY_GREEN = 'green'
|
|
51
|
+
VALID_STRATEGIES = {STRATEGY_BLUE, STRATEGY_GREEN}
|
|
52
|
+
|
|
53
|
+
# gRPC error codes and messages
|
|
54
|
+
GRPC_ERROR_STRATEGY_MISMATCH = 'status: 456'
|
|
55
|
+
GRPC_ERROR_SERVICE_UNAVAILABLE = 'status: 503'
|
|
56
|
+
GRPC_ERROR_ACCESS_DENIED = 'Access denied'
|
|
@@ -10,7 +10,13 @@ from thrift.transport import TTransport
|
|
|
10
10
|
|
|
11
11
|
from e6data_python_connector.e6x_vector.ttypes import Chunk, Vector, VectorType
|
|
12
12
|
from e6data_python_connector.constants import ZONE
|
|
13
|
-
from e6data_python_connector.date_time_utils import
|
|
13
|
+
from e6data_python_connector.date_time_utils import (
|
|
14
|
+
floor_div,
|
|
15
|
+
floor_mod,
|
|
16
|
+
timezone_from_offset,
|
|
17
|
+
format_iso_date_from_epoch_micros,
|
|
18
|
+
format_iso_datetime_from_epoch_micros,
|
|
19
|
+
)
|
|
14
20
|
|
|
15
21
|
# Try to import fastbinary - it's optional but provides better performance
|
|
16
22
|
_fastbinary_available = False
|
|
@@ -463,16 +469,11 @@ def read_values_from_array(query_columns_description: list, dis: DataInputStream
|
|
|
463
469
|
if dtype == "LONG":
|
|
464
470
|
value_array.append(dis.read_long())
|
|
465
471
|
elif dtype == "DATE":
|
|
466
|
-
|
|
467
|
-
date = datetime.fromtimestamp(epoch_seconds, ZONE)
|
|
468
|
-
value_array.append(date.strftime("%Y-%m-%d"))
|
|
472
|
+
value_array.append(format_iso_date_from_epoch_micros(dis.read_long()))
|
|
469
473
|
elif dtype == "DATETIME":
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
date_time = datetime.fromtimestamp(epoch_seconds, ZONE)
|
|
474
|
-
date_time = date_time + timedelta(microseconds=micros_of_the_day)
|
|
475
|
-
value_array.append(date_time.strftime("%Y-%m-%d %H:%M:%S"))
|
|
474
|
+
value_array.append(format_iso_datetime_from_epoch_micros(
|
|
475
|
+
dis.read_long(), tz=ZONE, separator=' ',
|
|
476
|
+
include_millis=False, include_offset=False))
|
|
476
477
|
elif dtype == "STRING" or dtype == "ARRAY" or dtype == "MAP" or dtype == "STRUCT":
|
|
477
478
|
value_array.append(dis.read_utf().decode())
|
|
478
479
|
elif dtype == "INT":
|
|
@@ -564,23 +565,27 @@ def get_column_from_chunk(vector: Vector) -> list:
|
|
|
564
565
|
if get_null(vector, row):
|
|
565
566
|
value_array.append(None)
|
|
566
567
|
continue
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
568
|
+
try:
|
|
569
|
+
raw = (vector.data.dateData.data[row]
|
|
570
|
+
if not vector.isConstantVector
|
|
571
|
+
else vector.data.dateConstantData.data)
|
|
572
|
+
value_array.append(format_iso_date_from_epoch_micros(raw))
|
|
573
|
+
except Exception as e:
|
|
574
|
+
_logger.error("Failed to parse DATE row=%s: %s", row, e)
|
|
575
|
+
value_array.append('Failed to parse.')
|
|
572
576
|
elif d_type == VectorType.DATETIME:
|
|
573
577
|
for row in range(vector.size):
|
|
574
578
|
if get_null(vector, row):
|
|
575
579
|
value_array.append(None)
|
|
576
580
|
continue
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
581
|
+
try:
|
|
582
|
+
epoch_micros = (vector.data.timeData.data[row]
|
|
583
|
+
if not vector.isConstantVector
|
|
584
|
+
else vector.data.timeConstantData.data)
|
|
585
|
+
value_array.append(format_iso_datetime_from_epoch_micros(epoch_micros, tz=zone))
|
|
586
|
+
except Exception as e:
|
|
587
|
+
_logger.error("Failed to parse DATETIME row=%s: %s", row, e)
|
|
588
|
+
value_array.append('Failed to parse.')
|
|
584
589
|
elif d_type == VectorType.STRING or d_type == VectorType.ARRAY or d_type == VectorType.MAP or d_type == VectorType.STRUCT:
|
|
585
590
|
for row in range(vector.size):
|
|
586
591
|
if get_null(vector, row):
|
|
@@ -631,18 +636,21 @@ def get_column_from_chunk(vector: Vector) -> list:
|
|
|
631
636
|
if get_null(vector, row):
|
|
632
637
|
value_array.append(None)
|
|
633
638
|
continue
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
639
|
+
try:
|
|
640
|
+
epoch_micros = (vector.data.timeData.data[row]
|
|
641
|
+
if not vector.isConstantVector
|
|
642
|
+
else vector.data.timeConstantData.data)
|
|
643
|
+
row_zone = zone
|
|
644
|
+
if ((vector.isConstantVector and vector.data.timeConstantData.zoneData is not None) or
|
|
645
|
+
(not vector.isConstantVector and vector.data.timeData.zoneData is not None)):
|
|
646
|
+
zone_id = (vector.data.timeData.zoneData[row]
|
|
647
|
+
if not vector.isConstantVector
|
|
648
|
+
else vector.data.timeConstantData.zoneData)
|
|
649
|
+
row_zone = timezone_from_offset(zone_id)
|
|
650
|
+
value_array.append(format_iso_datetime_from_epoch_micros(epoch_micros, tz=row_zone))
|
|
651
|
+
except Exception as e:
|
|
652
|
+
_logger.error("Failed to parse TIMESTAMP_TZ row=%s: %s", row, e)
|
|
653
|
+
value_array.append('Failed to parse.')
|
|
646
654
|
elif d_type == VectorType.DECIMAL128:
|
|
647
655
|
# Handle both constant and non-constant vectors following Java implementation
|
|
648
656
|
if vector.isConstantVector:
|
|
@@ -679,6 +687,8 @@ def get_column_from_chunk(vector: Vector) -> list:
|
|
|
679
687
|
else:
|
|
680
688
|
value_array.append(None)
|
|
681
689
|
except Exception as e:
|
|
682
|
-
_logger.error(
|
|
683
|
-
|
|
690
|
+
_logger.error("get_column_from_chunk failed (vectorType=%s, parsed=%s/%s): %s",
|
|
691
|
+
d_type, len(value_array), vector.size, e)
|
|
692
|
+
while len(value_array) < vector.size:
|
|
693
|
+
value_array.append('Failed to parse.')
|
|
684
694
|
return value_array
|
|
@@ -160,6 +160,101 @@ def floor_mod(x, y):
|
|
|
160
160
|
return r
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
_SECONDS_PER_DAY = 86400
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _civil_from_days(z):
|
|
167
|
+
"""
|
|
168
|
+
Convert days since 1970-01-01 to proleptic Gregorian (year, month, day).
|
|
169
|
+
|
|
170
|
+
Howard Hinnant's algorithm. Supports any year representable by a Python int,
|
|
171
|
+
so it does not overflow like datetime.fromtimestamp (capped at year 9999).
|
|
172
|
+
"""
|
|
173
|
+
z = z + 719468
|
|
174
|
+
era = (z if z >= 0 else z - 146096) // 146097
|
|
175
|
+
doe = z - era * 146097
|
|
176
|
+
yoe = (doe - doe // 1460 + doe // 36524 - doe // 146096) // 365
|
|
177
|
+
y = yoe + era * 400
|
|
178
|
+
doy = doe - (365 * yoe + yoe // 4 - yoe // 100)
|
|
179
|
+
mp = (5 * doy + 2) // 153
|
|
180
|
+
d = doy - (153 * mp + 2) // 5 + 1
|
|
181
|
+
m = mp + 3 if mp < 10 else mp - 9
|
|
182
|
+
if m <= 2:
|
|
183
|
+
y += 1
|
|
184
|
+
return y, m, d
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _format_iso_year(year):
|
|
188
|
+
"""Java LocalDate-style year: 0000-9999 unprefixed; outside range gets +/- prefix."""
|
|
189
|
+
if 0 <= year <= 9999:
|
|
190
|
+
return "{:04d}".format(year)
|
|
191
|
+
if year > 9999:
|
|
192
|
+
return "+{}".format(year)
|
|
193
|
+
return "-{:04d}".format(abs(year)) if abs(year) <= 9999 else "-{}".format(abs(year))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _tz_offset_minutes(tz, ref_seconds=0):
|
|
197
|
+
"""Best-effort fixed-offset extraction. Returns 0 if unknown."""
|
|
198
|
+
if tz is None:
|
|
199
|
+
return 0
|
|
200
|
+
try:
|
|
201
|
+
offset = tz.utcoffset(None)
|
|
202
|
+
if offset is not None:
|
|
203
|
+
return int(offset.total_seconds() // 60)
|
|
204
|
+
except (TypeError, AttributeError, ValueError):
|
|
205
|
+
pass
|
|
206
|
+
# Fallback for named timezones: probe with a reference datetime.
|
|
207
|
+
try:
|
|
208
|
+
from datetime import datetime as _dt
|
|
209
|
+
probe = _dt.utcfromtimestamp(max(0, min(ref_seconds, 253370764800))) # clamp to year 9999
|
|
210
|
+
offset = tz.utcoffset(probe)
|
|
211
|
+
if offset is not None:
|
|
212
|
+
return int(offset.total_seconds() // 60)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
return 0
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def format_iso_date_from_epoch_micros(epoch_micros):
|
|
219
|
+
"""
|
|
220
|
+
Format epoch microseconds (UTC) as ISO-8601 date with Java LocalDate-style
|
|
221
|
+
expanded year support (e.g. '+11756-12-31'). Never raises on year overflow.
|
|
222
|
+
"""
|
|
223
|
+
epoch_seconds = floor_div(epoch_micros, 1_000_000)
|
|
224
|
+
days = floor_div(epoch_seconds, _SECONDS_PER_DAY)
|
|
225
|
+
y, mo, d = _civil_from_days(days)
|
|
226
|
+
return "{}-{:02d}-{:02d}".format(_format_iso_year(y), mo, d)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def format_iso_datetime_from_epoch_micros(epoch_micros, tz=None, separator='T',
|
|
230
|
+
include_millis=True, include_offset=True):
|
|
231
|
+
"""
|
|
232
|
+
Format epoch microseconds as ISO-8601 datetime with expanded year support.
|
|
233
|
+
Mirrors Java's LocalDateTime/OffsetDateTime toString() semantics so out-of-range
|
|
234
|
+
years are emitted instead of raising.
|
|
235
|
+
"""
|
|
236
|
+
epoch_seconds_utc = floor_div(epoch_micros, 1_000_000)
|
|
237
|
+
offset_minutes = _tz_offset_minutes(tz, ref_seconds=epoch_seconds_utc)
|
|
238
|
+
local_micros = epoch_micros + offset_minutes * 60 * 1_000_000
|
|
239
|
+
epoch_seconds = floor_div(local_micros, 1_000_000)
|
|
240
|
+
micros_of_second = floor_mod(local_micros, 1_000_000)
|
|
241
|
+
days = floor_div(epoch_seconds, _SECONDS_PER_DAY)
|
|
242
|
+
sec_of_day = floor_mod(epoch_seconds, _SECONDS_PER_DAY)
|
|
243
|
+
y, mo, d = _civil_from_days(days)
|
|
244
|
+
h = sec_of_day // 3600
|
|
245
|
+
mi = (sec_of_day % 3600) // 60
|
|
246
|
+
s = sec_of_day % 60
|
|
247
|
+
out = "{}-{:02d}-{:02d}{}{:02d}:{:02d}:{:02d}".format(
|
|
248
|
+
_format_iso_year(y), mo, d, separator, h, mi, s)
|
|
249
|
+
if include_millis:
|
|
250
|
+
out += ".{:03d}".format(micros_of_second // 1000)
|
|
251
|
+
if include_offset:
|
|
252
|
+
sign = '+' if offset_minutes >= 0 else '-'
|
|
253
|
+
tot = abs(offset_minutes)
|
|
254
|
+
out += "{}{:02d}:{:02d}".format(sign, tot // 60, tot % 60)
|
|
255
|
+
return out
|
|
256
|
+
|
|
257
|
+
|
|
163
258
|
def timezone_from_offset(offset_string) -> pytz:
|
|
164
259
|
# Parse the offset string into hours and minutes
|
|
165
260
|
sign = -1 if offset_string[0] == "-" else 1
|
|
@@ -9,25 +9,27 @@ from __future__ import unicode_literals
|
|
|
9
9
|
import datetime
|
|
10
10
|
import logging
|
|
11
11
|
import os
|
|
12
|
-
|
|
13
12
|
import re
|
|
14
13
|
import sys
|
|
14
|
+
import threading
|
|
15
15
|
import time
|
|
16
16
|
from decimal import Decimal
|
|
17
17
|
from io import BytesIO
|
|
18
18
|
from ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
|
|
19
|
-
import threading
|
|
20
|
-
import multiprocessing
|
|
21
19
|
|
|
22
20
|
import grpc
|
|
23
21
|
from grpc._channel import _InactiveRpcError
|
|
24
22
|
|
|
25
23
|
from e6data_python_connector.cluster_manager import ClusterManager
|
|
26
|
-
from e6data_python_connector.
|
|
27
|
-
from e6data_python_connector.
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
from e6data_python_connector.common import DBAPITypeObject, ParamEscaper, DBAPICursor, get_ssl_credentials
|
|
25
|
+
from e6data_python_connector.constants import (
|
|
26
|
+
MAX_RETRY_ATTEMPTS, RETRY_SLEEP_SECONDS, GRPC_ERROR_STRATEGY_MISMATCH, GRPC_ERROR_ACCESS_DENIED,
|
|
27
|
+
PRIMITIVE_TYPES
|
|
28
|
+
)
|
|
29
|
+
from e6data_python_connector.datainputstream import get_query_columns_info, read_rows_from_chunk, \
|
|
30
|
+
is_fastbinary_available
|
|
30
31
|
from e6data_python_connector.server import e6x_engine_pb2_grpc, e6x_engine_pb2
|
|
32
|
+
from e6data_python_connector.strategy import _get_grpc_header
|
|
31
33
|
from e6data_python_connector.typeId import *
|
|
32
34
|
|
|
33
35
|
apilevel = '2.0'
|
|
@@ -71,7 +73,7 @@ TYPES_CONVERTER = {
|
|
|
71
73
|
|
|
72
74
|
def re_auth(func):
|
|
73
75
|
def wrapper(self, *args, **kwargs):
|
|
74
|
-
max_retry =
|
|
76
|
+
max_retry = MAX_RETRY_ATTEMPTS
|
|
75
77
|
current_retry = 0
|
|
76
78
|
while current_retry < max_retry:
|
|
77
79
|
try:
|
|
@@ -80,10 +82,10 @@ def re_auth(func):
|
|
|
80
82
|
current_retry += 1
|
|
81
83
|
if current_retry == max_retry:
|
|
82
84
|
raise e
|
|
83
|
-
if e.code() == grpc.StatusCode.INTERNAL and
|
|
84
|
-
time.sleep(
|
|
85
|
+
if e.code() == grpc.StatusCode.INTERNAL and GRPC_ERROR_ACCESS_DENIED in e.details():
|
|
86
|
+
time.sleep(RETRY_SLEEP_SECONDS)
|
|
85
87
|
self.connection.get_re_authenticate_session_id()
|
|
86
|
-
elif
|
|
88
|
+
elif GRPC_ERROR_STRATEGY_MISMATCH in e.details():
|
|
87
89
|
# Strategy changed, clear cache and retry
|
|
88
90
|
_clear_strategy_cache()
|
|
89
91
|
# Force re-authentication which will detect new strategy
|
|
@@ -304,12 +306,6 @@ def _get_strategy_debug_info():
|
|
|
304
306
|
}
|
|
305
307
|
|
|
306
308
|
|
|
307
|
-
def _get_grpc_header(engine_ip=None, cluster=None, strategy=None):
|
|
308
|
-
"""Generate gRPC metadata headers for the request."""
|
|
309
|
-
# Use the strategy module's implementation
|
|
310
|
-
return _get_strategy_header(engine_ip=engine_ip, cluster=cluster, strategy=strategy)
|
|
311
|
-
|
|
312
|
-
|
|
313
309
|
def connect(*args, **kwargs):
|
|
314
310
|
"""Constructor for creating a connection to the database. See class :py:class:`Connection` for
|
|
315
311
|
arguments.
|
|
@@ -516,45 +512,6 @@ class Connection(object):
|
|
|
516
512
|
|
|
517
513
|
return self._cached_grpc_options
|
|
518
514
|
|
|
519
|
-
def _get_ssl_credentials(self):
|
|
520
|
-
"""
|
|
521
|
-
Get SSL credentials for secure gRPC channel.
|
|
522
|
-
|
|
523
|
-
Handles three scenarios:
|
|
524
|
-
1. ssl_cert is a string (file path): Read the PEM certificate from the file
|
|
525
|
-
2. ssl_cert is bytes: Use the certificate content directly
|
|
526
|
-
3. ssl_cert is None: Use system default CA bundle
|
|
527
|
-
|
|
528
|
-
Returns:
|
|
529
|
-
grpc.ChannelCredentials: SSL credentials for secure channel
|
|
530
|
-
|
|
531
|
-
Raises:
|
|
532
|
-
FileNotFoundError: If ssl_cert is a file path but the file doesn't exist
|
|
533
|
-
IOError: If ssl_cert file cannot be read
|
|
534
|
-
"""
|
|
535
|
-
if self._ssl_cert is None:
|
|
536
|
-
# Use system default CA bundle
|
|
537
|
-
return grpc.ssl_channel_credentials()
|
|
538
|
-
elif isinstance(self._ssl_cert, str):
|
|
539
|
-
# ssl_cert is a file path - read the certificate from file
|
|
540
|
-
try:
|
|
541
|
-
with open(self._ssl_cert, 'rb') as cert_file:
|
|
542
|
-
root_ca_cert = cert_file.read()
|
|
543
|
-
return grpc.ssl_channel_credentials(root_certificates=root_ca_cert)
|
|
544
|
-
except FileNotFoundError:
|
|
545
|
-
logger.error(f"SSL certificate file not found: {self._ssl_cert}")
|
|
546
|
-
raise
|
|
547
|
-
except IOError as e:
|
|
548
|
-
logger.error(f"Failed to read SSL certificate file {self._ssl_cert}: {e}")
|
|
549
|
-
raise
|
|
550
|
-
elif isinstance(self._ssl_cert, bytes):
|
|
551
|
-
# ssl_cert is certificate content as bytes
|
|
552
|
-
return grpc.ssl_channel_credentials(root_certificates=self._ssl_cert)
|
|
553
|
-
else:
|
|
554
|
-
# Invalid type - log warning and use system default
|
|
555
|
-
logger.warning(f"Invalid ssl_cert type: {type(self._ssl_cert)}. Using system default CA bundle.")
|
|
556
|
-
return grpc.ssl_channel_credentials()
|
|
557
|
-
|
|
558
515
|
def _create_client(self):
|
|
559
516
|
"""
|
|
560
517
|
Creates a gRPC client for the connection.
|
|
@@ -575,7 +532,7 @@ class Connection(object):
|
|
|
575
532
|
self._channel = grpc.secure_channel(
|
|
576
533
|
target='{}:{}'.format(self._host, self._port),
|
|
577
534
|
options=self._get_grpc_options,
|
|
578
|
-
credentials=self.
|
|
535
|
+
credentials=get_ssl_credentials(self._ssl_cert)
|
|
579
536
|
)
|
|
580
537
|
else:
|
|
581
538
|
self._channel = grpc.insecure_channel(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: e6data-python-connector
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.13rc1
|
|
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
|
|
|
@@ -54,6 +54,10 @@ yum install python3-devel gcc-c++ -y
|
|
|
54
54
|
# Ubuntu/Debian dependencies
|
|
55
55
|
apt install python3-dev g++ -y
|
|
56
56
|
|
|
57
|
+
# Windows dependencies
|
|
58
|
+
# Install Visual C++ Build Tools from:
|
|
59
|
+
# https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
|
60
|
+
# Select the "Desktop Development with C++" option during installation.
|
|
57
61
|
|
|
58
62
|
# Pip dependencies
|
|
59
63
|
pip install wheel
|
|
@@ -45,6 +45,7 @@ test/analyze_all_cases.py
|
|
|
45
45
|
test/analyze_binary.py
|
|
46
46
|
test/analyze_correct_value.py
|
|
47
47
|
test/analyze_fields.py
|
|
48
|
+
test/benchmark_fastbinary_performance.py
|
|
48
49
|
test/check_decimal_errors.py
|
|
49
50
|
test/cleanup_test_files.py
|
|
50
51
|
test/debug_38_nines.py
|
|
@@ -57,6 +58,7 @@ test/test_all_decimal128_cases.py
|
|
|
57
58
|
test/test_cluster_manager_efficiency.py
|
|
58
59
|
test/test_cluster_manager_none_strategy.py
|
|
59
60
|
test/test_cluster_manager_strategy.py
|
|
61
|
+
test/test_code_optimizations.py
|
|
60
62
|
test/test_comprehensive.py
|
|
61
63
|
test/test_connection_pool.py
|
|
62
64
|
test/test_connection_pool_e2e.py
|
|
@@ -70,10 +72,12 @@ test/test_manual_analysis.py
|
|
|
70
72
|
test/test_mock_server.py
|
|
71
73
|
test/test_multiprocessing_fix.py
|
|
72
74
|
test/test_new_implementation.py
|
|
75
|
+
test/test_optimizations_integration.py
|
|
73
76
|
test/test_pool_concurrency_simple.py
|
|
74
77
|
test/test_pool_threading_multiprocessing.py
|
|
75
78
|
test/test_simple_connection_pool.py
|
|
76
79
|
test/test_specific_binary.py
|
|
80
|
+
test/test_ssl_cert.py
|
|
77
81
|
test/test_strategy.py
|
|
78
82
|
test/test_strategy_logic.py
|
|
79
83
|
test/test_strategy_persistence_fix.py
|