supertable 2.5.4__tar.gz → 2.5.6__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.
- {supertable-2.5.4/supertable.egg-info → supertable-2.5.6}/PKG-INFO +2 -2
- {supertable-2.5.4 → supertable-2.5.6}/README.md +1 -1
- {supertable-2.5.4 → supertable-2.5.6}/pyproject.toml +1 -1
- {supertable-2.5.4 → supertable-2.5.6}/supertable/__init__.py +41 -2
- {supertable-2.5.4 → supertable-2.5.6}/supertable/config/settings.py +12 -10
- {supertable-2.5.4 → supertable-2.5.6}/supertable/data_reader.py +93 -14
- {supertable-2.5.4 → supertable-2.5.6}/supertable/data_writer.py +2 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/adaptive_router.py +16 -1
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/data_estimator.py +67 -26
- supertable-2.5.6/supertable/engine/disk_admission.py +168 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/duckdb_engine.py +79 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/engine_common.py +49 -4
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/engine_config.py +102 -18
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/executor.py +260 -24
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/file_cache.py +170 -12
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/island_resources.py +488 -49
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/islanddb.py +1588 -143
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/query_observations.py +1 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/range_cache.py +29 -9
- {supertable-2.5.4 → supertable-2.5.6}/supertable/processing.py +114 -5
- {supertable-2.5.4 → supertable-2.5.6}/supertable/redis_catalog.py +47 -9
- {supertable-2.5.4 → supertable-2.5.6}/supertable/super_table.py +10 -8
- {supertable-2.5.4 → supertable-2.5.6/supertable.egg-info}/PKG-INFO +2 -2
- {supertable-2.5.4 → supertable-2.5.6}/supertable.egg-info/SOURCES.txt +1 -0
- {supertable-2.5.4 → supertable-2.5.6}/LICENSE +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/MANIFEST.in +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/requirements.txt +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/setup.cfg +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/setup.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/admin.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/chain.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/consumers.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/crypto.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/diagnostics.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/durable_journal.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/events.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/export.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/logger.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/middleware.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/privileged.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/privileged_outbox.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/privileged_worker.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/reader.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/retention.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/writer_parquet.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/audit/writer_redis.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/config/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/config/defaults.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/config/homedir.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/data_classes.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/README.md +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/annotations.json +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/core.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/defaults.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/demo_queries.md +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/export_accounting.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/finance_sources.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/generate.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/helpers.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/load.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/quality.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/run.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/transform.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/medcenter/validation.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/__main__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/check_filter_builder.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/controller.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/data_writer_helpers.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/defaults.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/dummy_data.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/read_parquet_header.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s01_01_01_create_super_table.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s01_01_02_enable_mirroring_formats.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s01_02_create_roles.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s01_03_create_users.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s02_01_write_dummy_data.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s02_02_write_single_data.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s02_03_01_write_staging.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s02_03_02_create_pipe.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s02_04_01_write_monitoring_simple.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s02_04_02_write_monitoring_parallel.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s02_05_write_tombstone.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_01_read_data_error.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_02_01_read_super_data_ok.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_02_02_read_table_data_ok.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_03_read_meta.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_04_read_staging.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_06_01_read_roles.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_06_02_read_user.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_07_01_estimate_read.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_07_02_estimate_files.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s03_08_read_snapshot_history.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s04_01_03_delete_pipe.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s05_01_delete_table.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/quickstart/s05_02_delete_super_table.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/webshop/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/webshop/core.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/webshop/defaults.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/webshop/generate.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/webshop/load.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/webshop/stream_demo.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/demo/webshop/topup.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/engine_enum.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/island_spill.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/join_pruner.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/plan_stats.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/remote_paths.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/spark_thrift.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/engine/stable_http_relay.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/errors.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/locking/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/locking/file_lock.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/locking/redis_lock.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/logging.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/meta_reader.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/mirroring/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/mirroring/failure_safety.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/mirroring/mirror_delta.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/mirroring/mirror_formats.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/mirroring/mirror_iceberg.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/mirroring/mirror_parquet.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/monitoring/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/monitoring/partitions.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/monitoring_writer.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/odata_continuation.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/plan_extender.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/anomaly.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/checker.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/config.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/cron.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/execution.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/history.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/scheduler.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/quality/serialization.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/query_plan_manager.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/rbac/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/rbac/access_control.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/rbac/filter_builder.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/rbac/permissions.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/rbac/role_manager.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/rbac/row_column_security.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/rbac/user_manager.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/recovery/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/recovery/redis_rebuild.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/redis_connector.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/redis_infra.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/redis_keys.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/row_identity.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/simple_table.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/staging_area.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/azure_storage.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/gcp_storage.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/local_storage.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/minio_storage.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/s3_storage.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/storage_factory.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/storage/storage_interface.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/super_pipe.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/system_query.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/tombstone_manifest_v2.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/__init__.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/diagnostic_redaction.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/helper.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/profiler.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/snapshot.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/spark_security.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/sql_parser.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable/utils/timer.py +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable.egg-info/dependency_links.txt +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable.egg-info/entry_points.txt +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable.egg-info/requires.txt +0 -0
- {supertable-2.5.4 → supertable-2.5.6}/supertable.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supertable
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.6
|
|
4
4
|
Summary: SuperTable — versioned data lake library for SQL analytics on Parquet + Redis.
|
|
5
5
|
Author-email: Levente Kupas <lkupas@kladnasoft.com>
|
|
6
6
|
License-Expression: LicenseRef-FSL-1.1-ALv2
|
|
@@ -54,7 +54,7 @@ Dynamic: license-file
|
|
|
54
54
|
|
|
55
55
|

|
|
56
56
|

|
|
57
|
-

|
|
58
58
|
|
|
59
59
|
**SuperTable — versioned data lake library for SQL analytics.**
|
|
60
60
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
**SuperTable — versioned data lake library for SQL analytics.**
|
|
8
8
|
|
|
@@ -25,11 +25,48 @@ See the ``supertable.demo`` package for runnable end-to-end demos and the
|
|
|
25
25
|
project documentation for the full API surface.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
__version__ = "2.5.
|
|
28
|
+
__version__ = "2.5.6"
|
|
29
29
|
|
|
30
|
+
import os
|
|
30
31
|
from importlib import import_module
|
|
31
32
|
from typing import TYPE_CHECKING, Any
|
|
32
33
|
|
|
34
|
+
|
|
35
|
+
def _bootstrap_island_polars_cpu_limit() -> None:
|
|
36
|
+
"""Apply an explicit Island CPU ceiling before Polars creates Rayon."""
|
|
37
|
+
raw = os.environ.get("SUPERTABLE_ISLAND_CPU_MAX")
|
|
38
|
+
if raw is None or not raw.strip():
|
|
39
|
+
return
|
|
40
|
+
try:
|
|
41
|
+
limit = int(raw.strip())
|
|
42
|
+
except ValueError:
|
|
43
|
+
raise RuntimeError(
|
|
44
|
+
"SUPERTABLE_ISLAND_CPU_MAX must be a non-negative integer"
|
|
45
|
+
) from None
|
|
46
|
+
if limit < 0:
|
|
47
|
+
raise RuntimeError(
|
|
48
|
+
"SUPERTABLE_ISLAND_CPU_MAX must be a non-negative integer"
|
|
49
|
+
)
|
|
50
|
+
if limit == 0:
|
|
51
|
+
return
|
|
52
|
+
existing_raw = os.environ.get("POLARS_MAX_THREADS")
|
|
53
|
+
if existing_raw is not None and existing_raw.strip():
|
|
54
|
+
try:
|
|
55
|
+
existing = int(existing_raw.strip())
|
|
56
|
+
except ValueError:
|
|
57
|
+
raise RuntimeError(
|
|
58
|
+
"POLARS_MAX_THREADS must be a positive integer"
|
|
59
|
+
) from None
|
|
60
|
+
if existing <= 0:
|
|
61
|
+
raise RuntimeError(
|
|
62
|
+
"POLARS_MAX_THREADS must be a positive integer"
|
|
63
|
+
)
|
|
64
|
+
limit = min(limit, existing)
|
|
65
|
+
os.environ["POLARS_MAX_THREADS"] = str(limit)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
_bootstrap_island_polars_cpu_limit()
|
|
69
|
+
|
|
33
70
|
# This public alias predates the ``supertable.engine`` implementation package.
|
|
34
71
|
# Importing the tiny enum after the now-lazy package is initialized ensures
|
|
35
72
|
# later imports of engine submodules cannot replace ``supertable.engine`` with
|
|
@@ -37,7 +74,9 @@ from typing import TYPE_CHECKING, Any
|
|
|
37
74
|
from supertable.engine.engine_enum import Engine as engine
|
|
38
75
|
|
|
39
76
|
|
|
40
|
-
# Importing the package is intentionally cheap and
|
|
77
|
+
# Importing the package is intentionally cheap and does not import native data
|
|
78
|
+
# engines. The only bootstrap side effect is the explicit Polars CPU ceiling
|
|
79
|
+
# above, which must exist before Rayon is initialized. The
|
|
41
80
|
# public convenience exports are loaded on first use (PEP 562) so importing
|
|
42
81
|
# ``supertable`` does not import PyArrow/Polars, connect to Redis, probe the
|
|
43
82
|
# filesystem, or initialise an application home.
|
|
@@ -263,7 +263,8 @@ class Settings:
|
|
|
263
263
|
# Parquet files; selective remote Island scans use the range cache below.
|
|
264
264
|
SUPERTABLE_ISLAND_CACHE_ENABLED: bool = True # SUPERTABLE_ISLAND_CACHE_ENABLED
|
|
265
265
|
SUPERTABLE_ISLAND_CACHE_DIR: str = "" # SUPERTABLE_ISLAND_CACHE_DIR
|
|
266
|
-
|
|
266
|
+
# Zero derives a safe share of currently free cache-filesystem capacity.
|
|
267
|
+
SUPERTABLE_ISLAND_CACHE_MAX_BYTES: int = 0 # SUPERTABLE_ISLAND_CACHE_MAX_BYTES
|
|
267
268
|
# Published Parquet objects are immutable/version-addressed. Capacity and
|
|
268
269
|
# integrity, rather than age, rotate the local object cache by default.
|
|
269
270
|
# Operators may still opt into an idle TTL with an explicitly positive
|
|
@@ -277,7 +278,7 @@ class Settings:
|
|
|
277
278
|
# for. Every cached interval is sealed to the provider version/ETag.
|
|
278
279
|
SUPERTABLE_ISLAND_RANGE_CACHE_ENABLED: bool = True # SUPERTABLE_ISLAND_RANGE_CACHE_ENABLED
|
|
279
280
|
SUPERTABLE_ISLAND_RANGE_CACHE_DIR: str = "" # SUPERTABLE_ISLAND_RANGE_CACHE_DIR
|
|
280
|
-
SUPERTABLE_ISLAND_RANGE_CACHE_MAX_BYTES: int =
|
|
281
|
+
SUPERTABLE_ISLAND_RANGE_CACHE_MAX_BYTES: int = 0 # SUPERTABLE_ISLAND_RANGE_CACHE_MAX_BYTES
|
|
281
282
|
# Immutable, version-sealed ranges rotate on capacity/corruption by default.
|
|
282
283
|
# A positive value explicitly restores idle-TTL eviction.
|
|
283
284
|
SUPERTABLE_ISLAND_RANGE_CACHE_TTL_SEC: int = 0 # SUPERTABLE_ISLAND_RANGE_CACHE_TTL_SEC
|
|
@@ -287,10 +288,11 @@ class Settings:
|
|
|
287
288
|
# quotas are hard; Polars/Arrow scan pools are process-global, so decoded
|
|
288
289
|
# scan-memory estimates are conservative routing/admission guards rather
|
|
289
290
|
# than an in-process per-query allocator cap.
|
|
290
|
-
SUPERTABLE_ISLAND_MEMORY_FRACTION: float = 0
|
|
291
|
-
SUPERTABLE_ISLAND_GLOBAL_MEMORY_FRACTION: float = 0
|
|
291
|
+
SUPERTABLE_ISLAND_MEMORY_FRACTION: float = 1.0 # SUPERTABLE_ISLAND_MEMORY_FRACTION
|
|
292
|
+
SUPERTABLE_ISLAND_GLOBAL_MEMORY_FRACTION: float = 1.0 # SUPERTABLE_ISLAND_GLOBAL_MEMORY_FRACTION
|
|
292
293
|
SUPERTABLE_ISLAND_MAX_MEMORY_BYTES: int = 0 # SUPERTABLE_ISLAND_MAX_MEMORY_BYTES
|
|
293
|
-
|
|
294
|
+
# Zero derives the effective container memory available to this process.
|
|
295
|
+
SUPERTABLE_ISLAND_MAX_RESULT_BYTES: int = 0 # SUPERTABLE_ISLAND_MAX_RESULT_BYTES
|
|
294
296
|
SUPERTABLE_ISLAND_CPU_MAX: int = 0 # SUPERTABLE_ISLAND_CPU_MAX
|
|
295
297
|
SUPERTABLE_ISLAND_IO_WORKERS_MAX: int = 16 # SUPERTABLE_ISLAND_IO_WORKERS_MAX
|
|
296
298
|
# Hard wall-clock bound for one admitted IslandDB execution. The bound is
|
|
@@ -637,7 +639,7 @@ def _build_settings() -> Settings:
|
|
|
637
639
|
SUPERTABLE_ISLAND_CACHE_ENABLED=_env_bool("SUPERTABLE_ISLAND_CACHE_ENABLED", True),
|
|
638
640
|
SUPERTABLE_ISLAND_CACHE_DIR=_env_str("SUPERTABLE_ISLAND_CACHE_DIR"),
|
|
639
641
|
SUPERTABLE_ISLAND_CACHE_MAX_BYTES=_env_int(
|
|
640
|
-
"SUPERTABLE_ISLAND_CACHE_MAX_BYTES",
|
|
642
|
+
"SUPERTABLE_ISLAND_CACHE_MAX_BYTES", 0,
|
|
641
643
|
),
|
|
642
644
|
SUPERTABLE_ISLAND_CACHE_TTL_SEC=_env_int(
|
|
643
645
|
"SUPERTABLE_ISLAND_CACHE_TTL_SEC", 0,
|
|
@@ -655,22 +657,22 @@ def _build_settings() -> Settings:
|
|
|
655
657
|
"SUPERTABLE_ISLAND_RANGE_CACHE_DIR",
|
|
656
658
|
),
|
|
657
659
|
SUPERTABLE_ISLAND_RANGE_CACHE_MAX_BYTES=_env_int(
|
|
658
|
-
"SUPERTABLE_ISLAND_RANGE_CACHE_MAX_BYTES",
|
|
660
|
+
"SUPERTABLE_ISLAND_RANGE_CACHE_MAX_BYTES", 0,
|
|
659
661
|
),
|
|
660
662
|
SUPERTABLE_ISLAND_RANGE_CACHE_TTL_SEC=_env_int(
|
|
661
663
|
"SUPERTABLE_ISLAND_RANGE_CACHE_TTL_SEC", 0,
|
|
662
664
|
),
|
|
663
665
|
SUPERTABLE_ISLAND_MEMORY_FRACTION=_env_float(
|
|
664
|
-
"SUPERTABLE_ISLAND_MEMORY_FRACTION", 0
|
|
666
|
+
"SUPERTABLE_ISLAND_MEMORY_FRACTION", 1.0,
|
|
665
667
|
),
|
|
666
668
|
SUPERTABLE_ISLAND_GLOBAL_MEMORY_FRACTION=_env_float(
|
|
667
|
-
"SUPERTABLE_ISLAND_GLOBAL_MEMORY_FRACTION", 0
|
|
669
|
+
"SUPERTABLE_ISLAND_GLOBAL_MEMORY_FRACTION", 1.0,
|
|
668
670
|
),
|
|
669
671
|
SUPERTABLE_ISLAND_MAX_MEMORY_BYTES=_env_int(
|
|
670
672
|
"SUPERTABLE_ISLAND_MAX_MEMORY_BYTES", 0,
|
|
671
673
|
),
|
|
672
674
|
SUPERTABLE_ISLAND_MAX_RESULT_BYTES=_env_int(
|
|
673
|
-
"SUPERTABLE_ISLAND_MAX_RESULT_BYTES",
|
|
675
|
+
"SUPERTABLE_ISLAND_MAX_RESULT_BYTES", 0,
|
|
674
676
|
),
|
|
675
677
|
SUPERTABLE_ISLAND_CPU_MAX=_env_int(
|
|
676
678
|
"SUPERTABLE_ISLAND_CPU_MAX", 0,
|
|
@@ -191,6 +191,9 @@ class _MonitoredResultStream:
|
|
|
191
191
|
self._finalize_complete = False
|
|
192
192
|
self._finalizing_thread: Optional[int] = None
|
|
193
193
|
self._finalize_error: BaseException | None = None
|
|
194
|
+
add_terminal_callback = getattr(inner, "add_terminal_callback", None)
|
|
195
|
+
if callable(add_terminal_callback):
|
|
196
|
+
add_terminal_callback(self._inner_terminal)
|
|
194
197
|
|
|
195
198
|
def __iter__(self):
|
|
196
199
|
return self
|
|
@@ -251,6 +254,29 @@ class _MonitoredResultStream:
|
|
|
251
254
|
with self._finalize_condition:
|
|
252
255
|
return self._finalized
|
|
253
256
|
|
|
257
|
+
def _inner_terminal(self, kind: str) -> None:
|
|
258
|
+
if kind == "completed":
|
|
259
|
+
status, message = Status.OK.value, None
|
|
260
|
+
elif kind == "timed_out":
|
|
261
|
+
status, message = Status.ERROR.value, "result stream timed out"
|
|
262
|
+
elif kind == "cancelled":
|
|
263
|
+
status, message = Status.ERROR.value, "result stream cancelled"
|
|
264
|
+
elif kind == "failed":
|
|
265
|
+
status, message = Status.ERROR.value, _QUERY_STREAM_ERROR
|
|
266
|
+
else:
|
|
267
|
+
status = Status.ERROR.value
|
|
268
|
+
message = "result stream closed before exhaustion"
|
|
269
|
+
try:
|
|
270
|
+
self._finish(status, message)
|
|
271
|
+
except BaseException as exc:
|
|
272
|
+
# A watcher-thread callback cannot raise into the request thread.
|
|
273
|
+
# _finish retains the error so any later stream operation observes
|
|
274
|
+
# it; protected failure audit emission has already been attempted.
|
|
275
|
+
logger.error(
|
|
276
|
+
"stream terminal monitoring failed; error_type=%s",
|
|
277
|
+
safe_exception_type(exc),
|
|
278
|
+
)
|
|
279
|
+
|
|
254
280
|
def __next__(self):
|
|
255
281
|
try:
|
|
256
282
|
batch = next(self._inner)
|
|
@@ -909,6 +935,9 @@ class DataReader:
|
|
|
909
935
|
actual_engine = str(
|
|
910
936
|
getattr(engine_used, "value", engine_used) or ""
|
|
911
937
|
)[:64]
|
|
938
|
+
audit_outcome = (
|
|
939
|
+
Outcome.SUCCESS if outcome == "success" else Outcome.FAILURE
|
|
940
|
+
)
|
|
912
941
|
emit(
|
|
913
942
|
category=EventCategory.DATA_ACCESS,
|
|
914
943
|
action=Actions.QUERY_EXECUTE,
|
|
@@ -930,7 +959,7 @@ class DataReader:
|
|
|
930
959
|
"column_count": max(0, int(result_columns)),
|
|
931
960
|
"outcome": outcome,
|
|
932
961
|
},
|
|
933
|
-
outcome=
|
|
962
|
+
outcome=audit_outcome,
|
|
934
963
|
)
|
|
935
964
|
|
|
936
965
|
def _assert_targets_exist(self, physical_tables) -> None:
|
|
@@ -1560,6 +1589,9 @@ class DataReader:
|
|
|
1560
1589
|
join_pruning_lanes=_engine_safe_join_pruning_lanes(engine),
|
|
1561
1590
|
plan_stats=self.plan_stats,
|
|
1562
1591
|
require_odata_identity=(_odata_identity is True),
|
|
1592
|
+
require_bounded_resource_estimates=(
|
|
1593
|
+
getattr(engine, "value", None) in {"auto", "islanddb"}
|
|
1594
|
+
),
|
|
1563
1595
|
)
|
|
1564
1596
|
if aggregate_children:
|
|
1565
1597
|
estimator_kwargs["aggregate_children"] = aggregate_children
|
|
@@ -2113,6 +2145,7 @@ class DataReader:
|
|
|
2113
2145
|
})
|
|
2114
2146
|
stream_timer.capture_and_reset_timing(event="EXECUTING_QUERY")
|
|
2115
2147
|
stream_timer.capture_duration(event="TOTAL_EXECUTE")
|
|
2148
|
+
monitoring_error: Optional[MonitoringPostExecutionError] = None
|
|
2116
2149
|
try:
|
|
2117
2150
|
extend_execution_plan(
|
|
2118
2151
|
query_plan_manager=stream_qpm,
|
|
@@ -2130,22 +2163,46 @@ class DataReader:
|
|
|
2130
2163
|
safe_cause = MonitoringDurabilityError(
|
|
2131
2164
|
"monitoring durability failure"
|
|
2132
2165
|
)
|
|
2133
|
-
|
|
2166
|
+
monitoring_error = MonitoringPostExecutionError(
|
|
2134
2167
|
organization=stream_qpm.organization,
|
|
2135
2168
|
super_name=stream_qpm.super_name,
|
|
2136
2169
|
query_id=str(getattr(stream_qpm, "query_id", "")),
|
|
2137
2170
|
status=final_status,
|
|
2138
2171
|
cause=safe_cause,
|
|
2139
|
-
)
|
|
2172
|
+
)
|
|
2140
2173
|
finally:
|
|
2141
2174
|
stream_timer.capture_and_reset_timing(event="EXTENDING_PLAN")
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2175
|
+
audit_engine = _actual_engine_from_plan_stats(
|
|
2176
|
+
stream_plan_stats,
|
|
2177
|
+
fallback=_engine_used,
|
|
2178
|
+
)
|
|
2179
|
+
try:
|
|
2180
|
+
if final_status == Status.OK.value:
|
|
2181
|
+
self._emit_successful_query_audit(
|
|
2182
|
+
role_name=role_name,
|
|
2183
|
+
engine_used=audit_engine,
|
|
2184
|
+
result_rows=result_rows,
|
|
2185
|
+
result_columns=result_columns,
|
|
2186
|
+
)
|
|
2187
|
+
else:
|
|
2188
|
+
self._emit_successful_query_audit(
|
|
2189
|
+
role_name=role_name,
|
|
2190
|
+
engine_used=audit_engine,
|
|
2191
|
+
result_rows=result_rows,
|
|
2192
|
+
result_columns=result_columns,
|
|
2193
|
+
outcome="failure",
|
|
2194
|
+
)
|
|
2195
|
+
except Exception:
|
|
2196
|
+
if final_status == Status.OK.value and monitoring_error is None:
|
|
2197
|
+
raise
|
|
2198
|
+
# Preserve the producer/cancellation or monitoring error
|
|
2199
|
+
# just as the materialized failure path does.
|
|
2200
|
+
logger.debug(
|
|
2201
|
+
self._lp("failed to emit query failure audit"),
|
|
2202
|
+
exc_info=True,
|
|
2148
2203
|
)
|
|
2204
|
+
if monitoring_error is not None:
|
|
2205
|
+
raise monitoring_error
|
|
2149
2206
|
|
|
2150
2207
|
return (
|
|
2151
2208
|
_MonitoredResultStream(result_value, _finalize_stream_monitoring),
|
|
@@ -2771,6 +2828,32 @@ def _engine_name(value: Any) -> str:
|
|
|
2771
2828
|
return str(raw or "").strip().casefold()
|
|
2772
2829
|
|
|
2773
2830
|
|
|
2831
|
+
def _actual_engine_from_plan_stats(plan_stats: Any, *, fallback: Any) -> str:
|
|
2832
|
+
"""Resolve a stream's engine only after deferred delivery has finished."""
|
|
2833
|
+
try:
|
|
2834
|
+
stats = tuple(
|
|
2835
|
+
entry for entry in (getattr(plan_stats, "stats", ()) or ())
|
|
2836
|
+
if isinstance(entry, dict)
|
|
2837
|
+
)
|
|
2838
|
+
outcome = _latest_plan_stat(stats, "AUTO_ROUTING_OUTCOME")
|
|
2839
|
+
if isinstance(outcome, dict):
|
|
2840
|
+
actual = _engine_name(outcome.get("actual_engine"))
|
|
2841
|
+
if actual:
|
|
2842
|
+
return actual
|
|
2843
|
+
recorded = _engine_name(_latest_plan_stat(stats, "ENGINE"))
|
|
2844
|
+
if recorded:
|
|
2845
|
+
return recorded
|
|
2846
|
+
for entry in reversed(stats):
|
|
2847
|
+
attempt = entry.get("ENGINE_ATTEMPT")
|
|
2848
|
+
if isinstance(attempt, dict):
|
|
2849
|
+
attempted = _engine_name(attempt.get("engine"))
|
|
2850
|
+
if attempted:
|
|
2851
|
+
return attempted
|
|
2852
|
+
except Exception:
|
|
2853
|
+
pass
|
|
2854
|
+
return _engine_name(fallback)
|
|
2855
|
+
|
|
2856
|
+
|
|
2774
2857
|
def _update_query_out(
|
|
2775
2858
|
reader: DataReader,
|
|
2776
2859
|
out: Optional[Dict[str, Any]],
|
|
@@ -2837,14 +2920,10 @@ def _update_query_out(
|
|
|
2837
2920
|
if not selected and isinstance(outcome, dict):
|
|
2838
2921
|
selected = _engine_name(outcome.get("selected_engine"))
|
|
2839
2922
|
|
|
2840
|
-
actual = ""
|
|
2923
|
+
actual = _actual_engine_from_plan_stats(plan_stats, fallback="")
|
|
2841
2924
|
fallback = False
|
|
2842
2925
|
if isinstance(outcome, dict):
|
|
2843
|
-
actual = _engine_name(outcome.get("actual_engine"))
|
|
2844
2926
|
fallback = bool(outcome.get("fallback", False))
|
|
2845
|
-
actual = actual or _engine_name(recorded_engine)
|
|
2846
|
-
if not actual and engine_attempts:
|
|
2847
|
-
actual = _engine_name(engine_attempts[-1].get("engine"))
|
|
2848
2927
|
selected = selected or actual or requested
|
|
2849
2928
|
if actual and selected and not isinstance(outcome, dict):
|
|
2850
2929
|
fallback = actual != selected
|
|
@@ -1902,6 +1902,8 @@ class DataWriter:
|
|
|
1902
1902
|
profiler=profiler,
|
|
1903
1903
|
existing_tombstones=prev_dv_df,
|
|
1904
1904
|
storage=self.super_table.storage,
|
|
1905
|
+
organization=self.super_table.organization,
|
|
1906
|
+
catalog=self.catalog,
|
|
1905
1907
|
require_global_tombstone_disjoint_proof=(
|
|
1906
1908
|
tombstone_v3_transition_enabled
|
|
1907
1909
|
),
|
|
@@ -138,6 +138,7 @@ class RoutingFeatures:
|
|
|
138
138
|
freshness_age_seconds: int = -1
|
|
139
139
|
has_active_tombstone: bool = False
|
|
140
140
|
streaming_result: bool = False
|
|
141
|
+
island_plan_evaluated: bool = False
|
|
141
142
|
island_advice: str = ""
|
|
142
143
|
island_cpu_workers: int = 0
|
|
143
144
|
island_io_workers: int = 0
|
|
@@ -193,6 +194,7 @@ class RoutingFeatures:
|
|
|
193
194
|
"stream" if self.streaming_result else "materialized",
|
|
194
195
|
"tombstone" if self.has_active_tombstone else "clean",
|
|
195
196
|
"fresh" if self.data_is_fresh else "stable",
|
|
197
|
+
"island_plan" if self.island_plan_evaluated else "no_island_plan",
|
|
196
198
|
str(self.island_advice or "none"),
|
|
197
199
|
str(self._magnitude_bucket(self.island_cpu_workers)),
|
|
198
200
|
str(self._magnitude_bucket(self.island_io_workers)),
|
|
@@ -502,10 +504,23 @@ class AdaptiveEngineRouter:
|
|
|
502
504
|
)
|
|
503
505
|
if not features.source_bytes_complete:
|
|
504
506
|
rejections[Engine.ISLANDDB].append("source byte estimate incomplete")
|
|
505
|
-
if
|
|
507
|
+
if (
|
|
508
|
+
features.island_plan_evaluated
|
|
509
|
+
and features.island_advice not in {
|
|
510
|
+
"island_in_memory", "island_spill",
|
|
511
|
+
}
|
|
512
|
+
):
|
|
506
513
|
rejections[Engine.ISLANDDB].append(
|
|
507
514
|
"IslandDB resource plan is not executable"
|
|
508
515
|
)
|
|
516
|
+
elif (
|
|
517
|
+
availability.island_enabled
|
|
518
|
+
and availability.island_supported
|
|
519
|
+
and not features.island_plan_evaluated
|
|
520
|
+
):
|
|
521
|
+
rejections[Engine.ISLANDDB].append(
|
|
522
|
+
"IslandDB resource plan is unavailable"
|
|
523
|
+
)
|
|
509
524
|
|
|
510
525
|
if not availability.spark_available:
|
|
511
526
|
rejections[Engine.SPARK_SQL].append("no safe fitting Spark cluster")
|
|
@@ -47,6 +47,7 @@ from supertable.utils.sql_parser import TableDefinition
|
|
|
47
47
|
from supertable.engine.join_pruner import prune_files_across_joins
|
|
48
48
|
from supertable.processing import (
|
|
49
49
|
integer_domains_from_complete_stats,
|
|
50
|
+
load_bounded_stats_for_planning,
|
|
50
51
|
load_stats,
|
|
51
52
|
prune_files_by_predicates,
|
|
52
53
|
resource_object_seal,
|
|
@@ -557,6 +558,7 @@ class DataEstimator:
|
|
|
557
558
|
Dict[Tuple[str, str], Iterable[str]]
|
|
558
559
|
] = None,
|
|
559
560
|
require_odata_identity: bool = False,
|
|
561
|
+
require_bounded_resource_estimates: bool = False,
|
|
560
562
|
):
|
|
561
563
|
self.organization = organization
|
|
562
564
|
self.storage = storage
|
|
@@ -577,6 +579,13 @@ class DataEstimator:
|
|
|
577
579
|
if type(require_odata_identity) is not bool:
|
|
578
580
|
raise TypeError("require_odata_identity must be a bool")
|
|
579
581
|
self.require_odata_identity = require_odata_identity
|
|
582
|
+
if type(require_bounded_resource_estimates) is not bool:
|
|
583
|
+
raise TypeError(
|
|
584
|
+
"require_bounded_resource_estimates must be a bool"
|
|
585
|
+
)
|
|
586
|
+
self.require_bounded_resource_estimates = (
|
|
587
|
+
require_bounded_resource_estimates
|
|
588
|
+
)
|
|
580
589
|
# Aggregate relations (``super_name == simple_name``) are resolved and
|
|
581
590
|
# authorized by DataReader before estimation. Pin that exact child set
|
|
582
591
|
# here so a table created between authorization and Redis SCAN cannot be
|
|
@@ -1684,11 +1693,13 @@ class DataEstimator:
|
|
|
1684
1693
|
return 0, False
|
|
1685
1694
|
widths: List[int] = []
|
|
1686
1695
|
for name in requested:
|
|
1687
|
-
|
|
1696
|
+
normalized_type = str(
|
|
1697
|
+
normalized_types.get(name) or ""
|
|
1698
|
+
).strip().casefold()
|
|
1699
|
+
width = self._decoded_fixed_width(normalized_type)
|
|
1688
1700
|
if (
|
|
1689
1701
|
width is None
|
|
1690
|
-
and
|
|
1691
|
-
== "binary"
|
|
1702
|
+
and normalized_type in {"binary", "string", "utf8"}
|
|
1692
1703
|
):
|
|
1693
1704
|
bound = (max_value_bytes or {}).get(name)
|
|
1694
1705
|
if (
|
|
@@ -1696,9 +1707,12 @@ class DataEstimator:
|
|
|
1696
1707
|
and not isinstance(bound, bool)
|
|
1697
1708
|
and bound >= 0
|
|
1698
1709
|
):
|
|
1699
|
-
#
|
|
1700
|
-
#
|
|
1701
|
-
|
|
1710
|
+
# Binary owns a 32-bit offset; Polars' native UTF-8 value
|
|
1711
|
+
# representation can retain a 16-byte string view. The
|
|
1712
|
+
# caller adds validity/alignment slack below.
|
|
1713
|
+
width = int(bound) + (
|
|
1714
|
+
4 if normalized_type == "binary" else 16
|
|
1715
|
+
)
|
|
1702
1716
|
if width is None:
|
|
1703
1717
|
return 0, False
|
|
1704
1718
|
widths.append(width)
|
|
@@ -2211,11 +2225,12 @@ class DataEstimator:
|
|
|
2211
2225
|
self.predicate_constraints.get(key)
|
|
2212
2226
|
)
|
|
2213
2227
|
|
|
2214
|
-
# Load the stats artifact ONCE per table and reuse it for
|
|
2215
|
-
# pruning, projection sizing
|
|
2216
|
-
#
|
|
2217
|
-
#
|
|
2218
|
-
#
|
|
2228
|
+
# Load the stats artifact ONCE per table and reuse it for
|
|
2229
|
+
# predicate pruning, projection sizing, cross-table join
|
|
2230
|
+
# propagation, and bounded engine resource planning. The
|
|
2231
|
+
# AUTO/IslandDB lane performs pre-decode admission; a normal
|
|
2232
|
+
# cache miss would otherwise whole-decode the artifact before
|
|
2233
|
+
# the cache byte cap can reject it.
|
|
2219
2234
|
stats_df: Optional["polars.DataFrame"] = None
|
|
2220
2235
|
stats_identity = (
|
|
2221
2236
|
stats_cache_identity(
|
|
@@ -2225,15 +2240,37 @@ class DataEstimator:
|
|
|
2225
2240
|
)
|
|
2226
2241
|
if stats_file else None
|
|
2227
2242
|
)
|
|
2228
|
-
if stats_file and (
|
|
2243
|
+
if stats_file and (
|
|
2244
|
+
need_projection
|
|
2245
|
+
or has_predicate
|
|
2246
|
+
or key in join_table_keys
|
|
2247
|
+
or bool(getattr(
|
|
2248
|
+
self, "require_bounded_resource_estimates", False,
|
|
2249
|
+
))
|
|
2250
|
+
):
|
|
2229
2251
|
try:
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2252
|
+
if bool(getattr(
|
|
2253
|
+
self, "require_bounded_resource_estimates", False,
|
|
2254
|
+
)):
|
|
2255
|
+
if expected_stats_rows is None:
|
|
2256
|
+
raise ValueError(
|
|
2257
|
+
"Statistics row count is unavailable"
|
|
2258
|
+
)
|
|
2259
|
+
stats_df = load_bounded_stats_for_planning(
|
|
2260
|
+
stats_file,
|
|
2261
|
+
expected_rows=expected_stats_rows,
|
|
2262
|
+
cache_identity=stats_identity,
|
|
2263
|
+
profiler=prune_profiler,
|
|
2264
|
+
storage=self.storage,
|
|
2265
|
+
)
|
|
2266
|
+
else:
|
|
2267
|
+
stats_df = load_stats(
|
|
2268
|
+
stats_file,
|
|
2269
|
+
allow_cache=True,
|
|
2270
|
+
cache_identity=stats_identity,
|
|
2271
|
+
profiler=prune_profiler,
|
|
2272
|
+
storage=self.storage,
|
|
2273
|
+
)
|
|
2237
2274
|
except Exception as stats_err:
|
|
2238
2275
|
# Stats are an optional optimisation artifact. A stale,
|
|
2239
2276
|
# corrupt, unavailable, or malformed pointer must never
|
|
@@ -2511,7 +2548,9 @@ class DataEstimator:
|
|
|
2511
2548
|
for file_key in survivors
|
|
2512
2549
|
)
|
|
2513
2550
|
for column_name, type_name in schema_types.items()
|
|
2514
|
-
if str(type_name).strip().casefold()
|
|
2551
|
+
if str(type_name).strip().casefold() in {
|
|
2552
|
+
"binary", "string", "utf8",
|
|
2553
|
+
}
|
|
2515
2554
|
and survivors
|
|
2516
2555
|
and all(
|
|
2517
2556
|
isinstance(resource_value_bounds.get(file_key), dict)
|
|
@@ -2700,11 +2739,11 @@ class DataEstimator:
|
|
|
2700
2739
|
# selected-RG anti join and a full-file uniqueness proof for
|
|
2701
2740
|
# every referenced immutable resource. Stats deliberately omit
|
|
2702
2741
|
# system columns, so use manifest row counts for decoded Int64
|
|
2703
|
-
# buffers
|
|
2704
|
-
#
|
|
2705
|
-
#
|
|
2706
|
-
#
|
|
2707
|
-
#
|
|
2742
|
+
# buffers. Conservatively charge one entire file for each rowid
|
|
2743
|
+
# consumer as compressed work: the candidate anti join and the
|
|
2744
|
+
# full identity proof. This is deliberately looser than the
|
|
2745
|
+
# actual range reads, but complete even when rowid chunks
|
|
2746
|
+
# dominate tiny user columns.
|
|
2708
2747
|
full_rows_complete = all(
|
|
2709
2748
|
isinstance(resource_rows.get(file_key), int)
|
|
2710
2749
|
and not isinstance(resource_rows.get(file_key), bool)
|
|
@@ -2734,7 +2773,9 @@ class DataEstimator:
|
|
|
2734
2773
|
else:
|
|
2735
2774
|
table_proof_decoded_complete = False
|
|
2736
2775
|
if full_sizes_complete:
|
|
2737
|
-
table_rg_bytes += sum(
|
|
2776
|
+
table_rg_bytes += 2 * sum(
|
|
2777
|
+
key_size[file_key] for file_key in survivors
|
|
2778
|
+
)
|
|
2738
2779
|
else:
|
|
2739
2780
|
table_rg_complete = False
|
|
2740
2781
|
table_decoded_bytes = (
|