e6data-python-connector 3.0.1rc3__tar.gz → 3.0.2__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-3.0.1rc3/e6data_python_connector.egg-info → e6data_python_connector-3.0.2}/PKG-INFO +19 -6
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/README.md +18 -5
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/async_cluster_manager.py +9 -4
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/async_connection.py +30 -8
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/async_connection_pool.py +2 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/async_cursor.py +3 -2
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/cluster_manager.py +4 -3
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/datainputstream.py +11 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/e6data_grpc.py +18 -13
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/e6x_vector/ttypes.py +3 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/result_decode.py +155 -65
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2/e6data_python_connector.egg-info}/PKG-INFO +19 -6
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/setup.py +1 -1
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_oauth_auto_resume.py +7 -2
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/LICENSE +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/MANIFEST.in +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/aio.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/async_dialect.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/async_oauth.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/async_work.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/cluster_server/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/cluster_server/cluster_pb2.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/cluster_server/cluster_pb2_grpc.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/common.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/connection_pool.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/constants.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/date_time_utils.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/dialect.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/e6x_vector/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/e6x_vector/constants.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/exceptions.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/oauth.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/oauth_cache.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/oauth_common.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/result_batch.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/result_decode_worker.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/result_prefetch.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/server/QueryEngineService.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/server/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/server/constants.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/server/e6x_engine_pb2.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/server/e6x_engine_pb2.pyi +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/server/e6x_engine_pb2_grpc.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/server/ttypes.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/strategy.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector/typeId.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector.egg-info/SOURCES.txt +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector.egg-info/dependency_links.txt +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector.egg-info/entry_points.txt +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector.egg-info/requires.txt +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/e6data_python_connector.egg-info/top_level.txt +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/pyproject.toml +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/setup.cfg +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/analyze_38_nines.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/analyze_all_cases.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/analyze_binary.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/analyze_correct_value.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/analyze_fields.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/check_decimal_errors.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/cleanup_test_files.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/conftest.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/debug_38_nines.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/debug_binary.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/final_test.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/__init__.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/benchmark_preflight.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/benchmark_result_batch_v2.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/benchmark_sync_async.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/expiry_preflight.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/result_batch_benchmark_support.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/support.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_async_connection.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_async_pool.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_async_queries.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_async_resume.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_async_sqlalchemy.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_async_token_lifecycle.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_e2e_auth_boundary.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_e2e_oauth_continuation.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_e2e_readonly_matrix.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_result_batch_v2.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_shared_token_issuance.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/test_sync_oauth_lifecycle.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/integration/token_observation.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/move_tests.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/quick_test.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_38_nines.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_all_decimal128_cases.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_cluster_manager_efficiency.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_cluster_manager_none_strategy.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_cluster_manager_oauth.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_cluster_manager_strategy.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_code_optimizations.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_comprehensive.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_connection_pool.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_connection_pool_e2e.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_current_implementation.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_decimal128_binary_parsing.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_decimal128_parsing.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_dialect_columns.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_fix.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_improved_parsing.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_known_case.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_manual_analysis.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_mock_server.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_multiprocessing_fix.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_new_implementation.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_oauth_authentication.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_oauth_resume_integration.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_pool_concurrency_simple.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_pool_threading_multiprocessing.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_simple_connection_pool.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_specific_binary.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_strategy.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_strategy_logic.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_strategy_persistence_fix.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_strategy_sharing_fix.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/test_user_binary_value.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/tests.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/tests_grpc.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/validate_decimal128.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/validate_implementation.py +0 -0
- {e6data_python_connector-3.0.1rc3 → e6data_python_connector-3.0.2}/test/verify_decimal_fix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: e6data-python-connector
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
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.
|
|
@@ -489,11 +489,15 @@ workers use the explicit `spawn` method; the application's global multiprocessin
|
|
|
489
489
|
start method is unchanged. An unguarded main script can run its other import
|
|
490
490
|
side effects, so keep all application startup inside the guard.
|
|
491
491
|
|
|
492
|
-
Both APIs default to
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
492
|
+
Both APIs default to unlimited gRPC send and receive message sizes (`-1`).
|
|
493
|
+
Explicit positive byte limits are still honored. For sync, set
|
|
494
|
+
`max_receive_message_length` and `max_send_message_length` in `grpc_options`.
|
|
495
|
+
For async, set the receive limit with `max_receive_message_bytes` or
|
|
496
|
+
`max_receive_message_length` in `grpc_options`; if both are supplied, they must
|
|
497
|
+
agree. Set `max_send_message_length` in `grpc_options` for the send limit.
|
|
498
|
+
The receive limit applies to each message after transport decompression, not the
|
|
499
|
+
total query result. Unlimited receiving does not limit client memory use;
|
|
500
|
+
continuous downloading can retain serialized batches while decoding catches up.
|
|
497
501
|
|
|
498
502
|
These examples use your existing `connection_options`, `sql`, and `consume`:
|
|
499
503
|
|
|
@@ -567,6 +571,15 @@ downloading is faster than decoding or application reads. Workers add Python
|
|
|
567
571
|
objects and process copies; the receive limit is not a limit on total memory.
|
|
568
572
|
`fetchall()` still retains the full result.
|
|
569
573
|
|
|
574
|
+
If a decoder worker exits or its local pipe breaks, the connector stops the
|
|
575
|
+
failed workers and decodes that complete serialized envelope once in the parent
|
|
576
|
+
process. It uses the same deadline and makes no extra result RPC. Continuous
|
|
577
|
+
downloading stays active and uncapped during this recovery. Later envelopes use
|
|
578
|
+
sequential decoding until the decoder runtime is replaced through its normal
|
|
579
|
+
lifecycle. Cancellation, expired deadlines, invalid worker replies, and errors
|
|
580
|
+
reported while decoding data are still failures. Their original cause is kept
|
|
581
|
+
on the first error delivered to the caller.
|
|
582
|
+
|
|
570
583
|
Only a V2 `UNIMPLEMENTED` response switches that query to V1 and disables its
|
|
571
584
|
optimizations. Other RPC failures reach the caller as the original
|
|
572
585
|
`grpc.RpcError` or `grpc.aio.AioRpcError`. A failed pending RPC is raised when
|
|
@@ -443,11 +443,15 @@ workers use the explicit `spawn` method; the application's global multiprocessin
|
|
|
443
443
|
start method is unchanged. An unguarded main script can run its other import
|
|
444
444
|
side effects, so keep all application startup inside the guard.
|
|
445
445
|
|
|
446
|
-
Both APIs default to
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
446
|
+
Both APIs default to unlimited gRPC send and receive message sizes (`-1`).
|
|
447
|
+
Explicit positive byte limits are still honored. For sync, set
|
|
448
|
+
`max_receive_message_length` and `max_send_message_length` in `grpc_options`.
|
|
449
|
+
For async, set the receive limit with `max_receive_message_bytes` or
|
|
450
|
+
`max_receive_message_length` in `grpc_options`; if both are supplied, they must
|
|
451
|
+
agree. Set `max_send_message_length` in `grpc_options` for the send limit.
|
|
452
|
+
The receive limit applies to each message after transport decompression, not the
|
|
453
|
+
total query result. Unlimited receiving does not limit client memory use;
|
|
454
|
+
continuous downloading can retain serialized batches while decoding catches up.
|
|
451
455
|
|
|
452
456
|
These examples use your existing `connection_options`, `sql`, and `consume`:
|
|
453
457
|
|
|
@@ -521,6 +525,15 @@ downloading is faster than decoding or application reads. Workers add Python
|
|
|
521
525
|
objects and process copies; the receive limit is not a limit on total memory.
|
|
522
526
|
`fetchall()` still retains the full result.
|
|
523
527
|
|
|
528
|
+
If a decoder worker exits or its local pipe breaks, the connector stops the
|
|
529
|
+
failed workers and decodes that complete serialized envelope once in the parent
|
|
530
|
+
process. It uses the same deadline and makes no extra result RPC. Continuous
|
|
531
|
+
downloading stays active and uncapped during this recovery. Later envelopes use
|
|
532
|
+
sequential decoding until the decoder runtime is replaced through its normal
|
|
533
|
+
lifecycle. Cancellation, expired deadlines, invalid worker replies, and errors
|
|
534
|
+
reported while decoding data are still failures. Their original cause is kept
|
|
535
|
+
on the first error delivered to the caller.
|
|
536
|
+
|
|
524
537
|
Only a V2 `UNIMPLEMENTED` response switches that query to V1 and disables its
|
|
525
538
|
optimizations. Other RPC failures reach the caller as the original
|
|
526
539
|
`grpc.RpcError` or `grpc.aio.AioRpcError`. A failed pending RPC is raised when
|
|
@@ -123,10 +123,15 @@ class AsyncClusterManager:
|
|
|
123
123
|
options = dict(grpc_options or {})
|
|
124
124
|
if any(key.removeprefix('grpc.') in ('ssl_target_name_override', 'default_authority') for key in options):
|
|
125
125
|
raise ValueError('TLS authority overrides are unsupported.')
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
for name in ('max_receive_message_length', 'max_send_message_length'):
|
|
127
|
+
limits = [options.pop(key) for key in (name, 'grpc.' + name) if key in options]
|
|
128
|
+
for limit in limits:
|
|
129
|
+
if (isinstance(limit, bool) or not isinstance(limit, int)
|
|
130
|
+
or (limit != -1 and limit <= 0)):
|
|
131
|
+
raise ValueError(name + ' must be -1 (unlimited) or a positive integer.')
|
|
132
|
+
if len(limits) == 2 and limits[0] != limits[1]:
|
|
133
|
+
raise ValueError('Conflicting ' + name + ' options.')
|
|
134
|
+
options['grpc.' + name] = limits[0] if limits else -1
|
|
130
135
|
self._host, self._port = host, port
|
|
131
136
|
self._user, self._password = user, password
|
|
132
137
|
self._secure_channel, self._ssl_cert = secure_channel, ssl_cert
|
|
@@ -14,6 +14,9 @@ from .oauth_common import validate_positive_timeout, validate_token_endpoint
|
|
|
14
14
|
from .server import e6x_engine_pb2 as pb, e6x_engine_pb2_grpc as bindings
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
_UNSET_RECEIVE_MESSAGE_BYTES = object()
|
|
18
|
+
|
|
19
|
+
|
|
17
20
|
@dataclass(frozen=True)
|
|
18
21
|
class QueryRoute:
|
|
19
22
|
target: str
|
|
@@ -50,7 +53,7 @@ class AsyncConnection:
|
|
|
50
53
|
token_url=None, oauth_scope=None, access_token=None,
|
|
51
54
|
client_auth_method='basic', *, operation_timeout=600.0,
|
|
52
55
|
oauth_timeout=10.0, cleanup_timeout=10.0, auto_resume_timeout=300.0,
|
|
53
|
-
max_receive_message_bytes=
|
|
56
|
+
max_receive_message_bytes=_UNSET_RECEIVE_MESSAGE_BYTES,
|
|
54
57
|
enable_result_batch_v2=False):
|
|
55
58
|
from .result_decode_worker import is_decode_worker
|
|
56
59
|
if is_decode_worker():
|
|
@@ -98,14 +101,31 @@ class AsyncConnection:
|
|
|
98
101
|
for name, value in [('operation_timeout', operation_timeout), ('oauth_timeout', oauth_timeout),
|
|
99
102
|
('cleanup_timeout', cleanup_timeout), ('auto_resume_timeout', auto_resume_timeout)]:
|
|
100
103
|
settings[name] = validate_positive_timeout(value, name)
|
|
101
|
-
if isinstance(max_receive_message_bytes, bool) or not isinstance(max_receive_message_bytes, int) or max_receive_message_bytes <= 0:
|
|
102
|
-
raise ValueError('max_receive_message_bytes must be a positive integer.')
|
|
103
104
|
options = dict(grpc_options or {})
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
receive_names = ('max_receive_message_length', 'grpc.max_receive_message_length')
|
|
106
|
+
receive_values = [options.pop(name) for name in receive_names if name in options]
|
|
107
|
+
for value in receive_values:
|
|
108
|
+
if (isinstance(value, bool) or not isinstance(value, int)
|
|
109
|
+
or (value != -1 and value <= 0)):
|
|
110
|
+
raise ValueError('max_receive_message_length must be -1 (unlimited) or a positive integer.')
|
|
111
|
+
if len(receive_values) == 2 and receive_values[0] != receive_values[1]:
|
|
112
|
+
raise ValueError('Conflicting max_receive_message_length options.')
|
|
113
|
+
if max_receive_message_bytes is _UNSET_RECEIVE_MESSAGE_BYTES:
|
|
114
|
+
max_receive_message_bytes = receive_values[0] if receive_values else -1
|
|
115
|
+
if (isinstance(max_receive_message_bytes, bool) or not isinstance(max_receive_message_bytes, int)
|
|
116
|
+
or (max_receive_message_bytes != -1 and max_receive_message_bytes <= 0)):
|
|
117
|
+
raise ValueError('max_receive_message_bytes must be -1 (unlimited) or a positive integer.')
|
|
118
|
+
if receive_values and receive_values[0] != max_receive_message_bytes:
|
|
119
|
+
raise ValueError('Set the receive limit using max_receive_message_bytes; supplied options must agree.')
|
|
120
|
+
send_names = ('max_send_message_length', 'grpc.max_send_message_length')
|
|
121
|
+
send_values = [options.pop(name) for name in send_names if name in options]
|
|
122
|
+
for value in send_values:
|
|
123
|
+
if (isinstance(value, bool) or not isinstance(value, int)
|
|
124
|
+
or (value != -1 and value <= 0)):
|
|
125
|
+
raise ValueError('max_send_message_length must be -1 (unlimited) or a positive integer.')
|
|
126
|
+
if len(send_values) == 2 and send_values[0] != send_values[1]:
|
|
127
|
+
raise ValueError('Conflicting max_send_message_length options.')
|
|
128
|
+
options['grpc.max_send_message_length'] = send_values[0] if send_values else -1
|
|
109
129
|
# Authority overrides can disable meaningful server identity validation.
|
|
110
130
|
if any(name.removeprefix('grpc.') in ('ssl_target_name_override', 'default_authority') for name in options):
|
|
111
131
|
raise ValueError('TLS authority overrides are unsupported.')
|
|
@@ -385,6 +405,8 @@ class AsyncConnection:
|
|
|
385
405
|
|
|
386
406
|
self._cluster_manager = AsyncClusterManager(
|
|
387
407
|
self.host, self.port, user=self.username or '', password=self.password or '',
|
|
408
|
+
grpc_options={**self.grpc_options,
|
|
409
|
+
'grpc.max_receive_message_length': self.max_receive_message_bytes},
|
|
388
410
|
secure_channel=self.secure, cluster_uuid=self.cluster_name,
|
|
389
411
|
ssl_cert=self.ssl_cert, metadata_provider=metadata_provider if self._oauth_enabled else None,
|
|
390
412
|
initial_strategy=self.strategy, auto_resume_timeout=self.auto_resume_timeout,
|
|
@@ -189,6 +189,8 @@ class AsyncConnectionPool:
|
|
|
189
189
|
from .async_cluster_manager import AsyncClusterManager
|
|
190
190
|
self._cluster_manager = AsyncClusterManager(
|
|
191
191
|
config.host, config.port, user=config.username or '', password=config.password or '',
|
|
192
|
+
grpc_options={**config.grpc_options,
|
|
193
|
+
'grpc.max_receive_message_length': config.max_receive_message_bytes},
|
|
192
194
|
secure_channel=config.secure, ssl_cert=config.ssl_cert,
|
|
193
195
|
cluster_uuid=config.cluster_name, initial_strategy='blue',
|
|
194
196
|
metadata_provider=self._resume_metadata if config._oauth_enabled else None,
|
|
@@ -129,7 +129,7 @@ class AsyncCursor:
|
|
|
129
129
|
if self._state == 'CLOSED':
|
|
130
130
|
raise ProgrammingError('Cursor is closed.')
|
|
131
131
|
if fetch and self._failure is not None:
|
|
132
|
-
raise self._failure
|
|
132
|
+
raise IncompleteResultError(self._failure.reason, query_id=self._failure.query_id)
|
|
133
133
|
if fetch and self._state in ('EMPTY', 'SUBMISSION_UNKNOWN'):
|
|
134
134
|
raise ProgrammingError('No complete query is available for fetching.')
|
|
135
135
|
|
|
@@ -172,12 +172,13 @@ class AsyncCursor:
|
|
|
172
172
|
self._retire_prefetch()
|
|
173
173
|
self._cancel_decode()
|
|
174
174
|
if self._failure is None:
|
|
175
|
+
# Never raise this template: its traceback would retain decoded inputs.
|
|
175
176
|
self._failure = IncompleteResultError(reason, query_id=self.query_id)
|
|
176
177
|
if self._state != 'CLOSED':
|
|
177
178
|
self._state = 'RESULT_FAILED'
|
|
178
179
|
self._rows.clear()
|
|
179
180
|
self._result_batches.clear()
|
|
180
|
-
return self._failure
|
|
181
|
+
return IncompleteResultError(self._failure.reason, query_id=self._failure.query_id)
|
|
181
182
|
|
|
182
183
|
def _accept_rows(self, rows):
|
|
183
184
|
self._rows.extend(rows)
|
|
@@ -205,9 +205,10 @@ class ClusterManager:
|
|
|
205
205
|
self._timeout = time.time() + timeout
|
|
206
206
|
self._secure_channel = secure_channel
|
|
207
207
|
self.cluster_uuid = cluster_uuid
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
options = dict(grpc_options or ())
|
|
209
|
+
options.setdefault('grpc.max_receive_message_length', -1)
|
|
210
|
+
options.setdefault('grpc.max_send_message_length', -1)
|
|
211
|
+
self._grpc_options = list(options.items())
|
|
211
212
|
self._debug = debug
|
|
212
213
|
self._ssl_cert = ssl_cert
|
|
213
214
|
self._metadata_provider = metadata_provider
|
|
@@ -478,6 +478,8 @@ def read_values_from_array(query_columns_description: list, dis: DataInputStream
|
|
|
478
478
|
try:
|
|
479
479
|
if dtype == "LONG":
|
|
480
480
|
value_array.append(dis.read_long())
|
|
481
|
+
elif dtype == "INTERVAL_DAY_SECOND":
|
|
482
|
+
value_array.append(timedelta(microseconds=dis.read_long()))
|
|
481
483
|
elif dtype == "DATE":
|
|
482
484
|
value_array.append(format_iso_date_from_epoch_micros(dis.read_long()))
|
|
483
485
|
elif dtype == "DATETIME":
|
|
@@ -582,6 +584,15 @@ def get_column_from_chunk(vector: Vector, strict=False) -> list:
|
|
|
582
584
|
continue
|
|
583
585
|
value_array.append(vector.data.int64Data.data[
|
|
584
586
|
row] if not vector.isConstantVector else vector.data.numericConstantData.data)
|
|
587
|
+
elif d_type == VectorType.INTERVAL_DAY_SECOND:
|
|
588
|
+
for row in range(vector.size):
|
|
589
|
+
if get_null(vector, row):
|
|
590
|
+
value_array.append(None)
|
|
591
|
+
continue
|
|
592
|
+
microseconds = (vector.data.int64Data.data[row]
|
|
593
|
+
if not vector.isConstantVector
|
|
594
|
+
else vector.data.numericConstantData.data)
|
|
595
|
+
value_array.append(timedelta(microseconds=microseconds))
|
|
585
596
|
elif d_type == VectorType.DATE:
|
|
586
597
|
# Use the JDBC-parity formatter so years > 9999 emit "+YYYYY-MM-DD"
|
|
587
598
|
# instead of raising. Per-row try/except keeps a single bad value
|
|
@@ -521,17 +521,18 @@ class Connection(object):
|
|
|
521
521
|
if enable_result_batch_v2:
|
|
522
522
|
from .result_decode import validate_decode_runtime
|
|
523
523
|
validate_decode_runtime()
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
524
|
+
normalized = {}
|
|
525
|
+
for key, value in (grpc_options or {}).items():
|
|
526
|
+
key = key[5:] if key.startswith('grpc.') else key
|
|
527
|
+
if key in normalized and normalized[key] != value:
|
|
528
|
+
raise ValueError('Conflicting gRPC options after prefix normalization.')
|
|
529
|
+
normalized[key] = value
|
|
530
|
+
grpc_options = normalized
|
|
531
|
+
if enable_result_batch_v2:
|
|
532
|
+
limit = normalized.get('max_receive_message_length', -1)
|
|
533
|
+
if isinstance(limit, bool) or not isinstance(limit, int) or (limit != -1 and limit <= 0):
|
|
534
|
+
raise ValueError('V2 receive limit must be a positive integer or -1 for unlimited.')
|
|
533
535
|
normalized['max_receive_message_length'] = limit
|
|
534
|
-
grpc_options = normalized
|
|
535
536
|
if enable_result_batch_v2 and grpc_options is not None and 'grpc_prepare_timeout' in grpc_options:
|
|
536
537
|
timeout = grpc_options['grpc_prepare_timeout']
|
|
537
538
|
if (isinstance(timeout, bool) or not isinstance(timeout, (int, float))
|
|
@@ -696,7 +697,7 @@ class Connection(object):
|
|
|
696
697
|
default_options = {
|
|
697
698
|
"keepalive_timeout_ms": 900000, # Time in milliseconds to keep the connection alive.
|
|
698
699
|
"max_receive_message_length": -1, # Maximum size of received messages.
|
|
699
|
-
"max_send_message_length":
|
|
700
|
+
"max_send_message_length": -1, # Unlimited sent message size.
|
|
700
701
|
"grpc_prepare_timeout": self.grpc_prepare_timeout, # Timeout for prepare statement API call.
|
|
701
702
|
"keepalive_time_ms": 30000, # Time in milliseconds between keep-alive pings.
|
|
702
703
|
"keepalive_permit_without_calls": 1, # Allow keep-alives with no active RPCs.
|
|
@@ -1032,6 +1033,7 @@ class Connection(object):
|
|
|
1032
1033
|
cluster_uuid=self.cluster_name,
|
|
1033
1034
|
timeout=self.grpc_auto_resume_timeout_seconds,
|
|
1034
1035
|
debug=self._debug,
|
|
1036
|
+
grpc_options=self._get_grpc_options,
|
|
1035
1037
|
ssl_cert=self._ssl_cert
|
|
1036
1038
|
).resume()
|
|
1037
1039
|
return status # Return boolean status directly
|
|
@@ -1709,7 +1711,9 @@ class Cursor(DBAPICursor):
|
|
|
1709
1711
|
self._result_failure = IncompleteResultError(reason, query_id=self._query_id)
|
|
1710
1712
|
self._data = None
|
|
1711
1713
|
self._result_batches.clear()
|
|
1712
|
-
|
|
1714
|
+
# Keep the cached template free of tracebacks and response frames.
|
|
1715
|
+
return IncompleteResultError(
|
|
1716
|
+
self._result_failure.reason, query_id=self._result_failure.query_id)
|
|
1713
1717
|
|
|
1714
1718
|
def _check_result(self):
|
|
1715
1719
|
if self._result_batch_v2_enabled and (self._result_pid != os.getpid() or
|
|
@@ -1720,7 +1724,8 @@ class Cursor(DBAPICursor):
|
|
|
1720
1724
|
if self._closed:
|
|
1721
1725
|
raise ProgrammingError('Cursor is closed.')
|
|
1722
1726
|
if self._result_failure is not None:
|
|
1723
|
-
raise
|
|
1727
|
+
raise IncompleteResultError(
|
|
1728
|
+
self._result_failure.reason, query_id=self._result_failure.query_id)
|
|
1724
1729
|
|
|
1725
1730
|
@property
|
|
1726
1731
|
def query_id(self):
|
|
@@ -35,6 +35,7 @@ class VectorType(object):
|
|
|
35
35
|
NULL = 14
|
|
36
36
|
TIMESTAMP_TZ = 15
|
|
37
37
|
DECIMAL128 = 16
|
|
38
|
+
INTERVAL_DAY_SECOND = 17
|
|
38
39
|
|
|
39
40
|
_VALUES_TO_NAMES = {
|
|
40
41
|
0: "BINARY",
|
|
@@ -54,6 +55,7 @@ class VectorType(object):
|
|
|
54
55
|
14: "NULL",
|
|
55
56
|
15: "TIMESTAMP_TZ",
|
|
56
57
|
16: "DECIMAL128",
|
|
58
|
+
17: "INTERVAL_DAY_SECOND",
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
_NAMES_TO_VALUES = {
|
|
@@ -74,6 +76,7 @@ class VectorType(object):
|
|
|
74
76
|
"NULL": 14,
|
|
75
77
|
"TIMESTAMP_TZ": 15,
|
|
76
78
|
"DECIMAL128": 16,
|
|
79
|
+
"INTERVAL_DAY_SECOND": 17,
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
|