supertable 2.2.5__tar.gz → 2.2.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.2.5/supertable.egg-info → supertable-2.2.6}/PKG-INFO +1 -1
- {supertable-2.2.5 → supertable-2.2.6}/pyproject.toml +1 -1
- {supertable-2.2.5 → supertable-2.2.6}/setup.py +1 -1
- {supertable-2.2.5 → supertable-2.2.6}/supertable/__init__.py +1 -1
- supertable-2.2.6/supertable/engine/engine_config.py +228 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/executor.py +10 -6
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/spark_thrift.py +9 -3
- {supertable-2.2.5 → supertable-2.2.6}/supertable/redis_catalog.py +35 -27
- {supertable-2.2.5 → supertable-2.2.6}/supertable/redis_keys.py +21 -12
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_redis_key_prefix.py +3 -3
- {supertable-2.2.5 → supertable-2.2.6/supertable.egg-info}/PKG-INFO +1 -1
- supertable-2.2.5/supertable/engine/engine_config.py +0 -153
- {supertable-2.2.5 → supertable-2.2.6}/LICENSE +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/README.md +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/requirements.txt +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/setup.cfg +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/admin.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/chain.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/consumers.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/crypto.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/events.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/export.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/logger.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/middleware.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/reader.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/retention.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/tests/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/tests/test_chain.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/tests/test_crypto.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/tests/test_emit.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/tests/test_events.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/tests/test_retention.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/writer_parquet.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/audit/writer_redis.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/defaults.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/homedir.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/settings.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/tests/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/tests/test_defaults.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/tests/test_homedir.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/config/tests/test_settings.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/data_classes.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/data_reader.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/data_writer.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/__main__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/check_filter_builder.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/controller.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/data_writer_helpers.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/defaults.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/dummy_data.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/read_parquet_header.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s01_01_01_create_super_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s01_01_02_enable_mirroring_formats.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s01_02_create_roles.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s01_03_create_users.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s02_01_write_dummy_data.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s02_02_write_single_data.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s02_03_01_write_staging.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s02_03_02_create_pipe.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s02_04_01_write_monitoring_simple.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s02_04_02_write_monitoring_parallel.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s02_05_write_tombstone.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_01_read_data_error.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_02_01_read_super_data_ok.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_02_02_read_table_data_ok.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_03_read_meta.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_04_read_staging.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_06_01_read_roles.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_06_02_read_user.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_07_01_estimate_read.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_07_02_estimate_files.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s03_08_read_snapshot_history.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s04_01_03_delete_pipe.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s05_01_delete_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/quickstart/s05_02_delete_super_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/webshop/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/webshop/core.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/webshop/defaults.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/webshop/generate.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/webshop/load.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/demo/webshop/topup.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/data_estimator.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/duckdb_lite.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/duckdb_pro.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/engine_common.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/engine_enum.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/plan_stats.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/tests/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/tests/conftest.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/tests/test_dedup_read.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/engine/tests/test_engine.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/errors.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/benchmarks/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/benchmarks/benchmark_locking.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/benchmarks/measure_lock_speed.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/benchmarks/measure_lock_time.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/file_lock.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/redis_lock.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/tests/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/tests/test_file_lock.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/locking/tests/test_redis_lock.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/logging.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/meta_reader.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/mirroring/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/mirroring/mirror_delta.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/mirroring/mirror_formats.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/mirroring/mirror_iceberg.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/mirroring/mirror_parquet.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/monitoring/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/monitoring/partitions.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/monitoring_writer.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/plan_extender.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/processing.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/query_plan_manager.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/access_control.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/filter_builder.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/permissions.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/role_manager.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/row_column_security.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/tests/test_filter_builder.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/tests/test_rbac.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/tests/test_rbac_per_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/rbac/user_manager.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/redis_connector.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/redis_infra.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/simple_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/staging_area.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/azure_storage.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/gcp_storage.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/local_storage.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/minio_storage.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/s3_storage.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/storage_factory.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/storage_interface.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/storage/tests/test_storage.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/super_pipe.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/super_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_align_to_schema_fix.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_create_if_missing.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_data_reader.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_data_reader_preflight.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_data_writer.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_data_writer_compact.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_data_writer_comprehensive.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_data_writer_tombstones.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_dedup_on_read_write.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_errors.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_meta_reader.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_monitoring_partitions.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_monitoring_sink_guard.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_newer_than.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_process_delete_only.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_processing.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_processing_compact_resources.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_query_sql.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_simple_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_small_file_compaction.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_super_table.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/tests/test_supertable_all.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/utils/__init__.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/utils/helper.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/utils/profiler.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/utils/sql_parser.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/utils/tests/test_sql_parser_columns.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable/utils/timer.py +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable.egg-info/SOURCES.txt +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable.egg-info/dependency_links.txt +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable.egg-info/entry_points.txt +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable.egg-info/requires.txt +0 -0
- {supertable-2.2.5 → supertable-2.2.6}/supertable.egg-info/top_level.txt +0 -0
|
@@ -19,7 +19,7 @@ long_description = readme.read_text(encoding="utf-8") if readme.exists() else ""
|
|
|
19
19
|
|
|
20
20
|
setup(
|
|
21
21
|
name="supertable",
|
|
22
|
-
version="2.2.
|
|
22
|
+
version="2.2.6",
|
|
23
23
|
description="SuperTable — versioned data lake library for SQL analytics on Parquet + Redis.",
|
|
24
24
|
long_description=long_description,
|
|
25
25
|
long_description_content_type="text/markdown",
|
|
@@ -25,7 +25,7 @@ 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.2.
|
|
28
|
+
__version__ = "2.2.6"
|
|
29
29
|
|
|
30
30
|
# Re-export the core public surface so users can do ``from supertable import …``
|
|
31
31
|
# instead of remembering submodule paths.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# route: supertable.engine.engine_config
|
|
2
|
+
"""
|
|
3
|
+
Engine runtime configuration — single source of truth for resolution.
|
|
4
|
+
|
|
5
|
+
Engine config is stored once per organization at **system scope**
|
|
6
|
+
(``supertable:{org}:system:engine:duckdb``). It is global to the org, not
|
|
7
|
+
per-supertable, and is resolved **live on every query** (no caching) so that
|
|
8
|
+
changes made in the UI take effect on the next query without a restart or a
|
|
9
|
+
connection reset.
|
|
10
|
+
|
|
11
|
+
Two kinds of settings live in that document:
|
|
12
|
+
|
|
13
|
+
* **Shared auto-pick thresholds** (one value, used by the router):
|
|
14
|
+
``engine_lite_max_bytes``, ``engine_spark_min_bytes``,
|
|
15
|
+
``engine_freshness_sec``. These describe the *boundaries* between engines
|
|
16
|
+
so they cannot be per-engine.
|
|
17
|
+
* **Per-engine DuckDB runtime pragmas** (separate values for Lite and Pro):
|
|
18
|
+
``duckdb_memory_limit``, ``duckdb_io_multiplier``, ``duckdb_threads``,
|
|
19
|
+
``duckdb_http_timeout``, ``duckdb_external_cache_size``. Each DuckDB engine
|
|
20
|
+
carries its own copy under a ``"lite"`` / ``"pro"`` section, so tuning Lite
|
|
21
|
+
never changes Pro and vice-versa.
|
|
22
|
+
|
|
23
|
+
Stored document shape::
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
"engine_lite_max_bytes": "104857600",
|
|
27
|
+
"engine_spark_min_bytes": "10737418240",
|
|
28
|
+
"engine_freshness_sec": "300",
|
|
29
|
+
"lite": {"duckdb_memory_limit": "1GB", ...},
|
|
30
|
+
"pro": {"duckdb_memory_limit": "8GB", ...},
|
|
31
|
+
"modified_ms": 1750000000000
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Resolution precedence, per field::
|
|
35
|
+
|
|
36
|
+
Redis (org system config) → environment variable → built-in default
|
|
37
|
+
|
|
38
|
+
The environment variable is a single global fallback shared by both engines;
|
|
39
|
+
per-engine differentiation therefore only happens once a value is stored in
|
|
40
|
+
Redis (via the UI).
|
|
41
|
+
|
|
42
|
+
Entry points (all share that precedence so engine and UI never disagree):
|
|
43
|
+
|
|
44
|
+
* :func:`resolve_engine_configs` → ``{"lite": cfg, "pro": cfg}`` in a
|
|
45
|
+
single Redis read; used by the
|
|
46
|
+
executor.
|
|
47
|
+
* :func:`resolve_engine_config` → typed :class:`EngineRuntimeConfig`
|
|
48
|
+
for one engine (convenience).
|
|
49
|
+
* :func:`resolve_engine_config_provenance` → ``{shared, lite, pro,
|
|
50
|
+
modified_ms}`` per-field
|
|
51
|
+
``{value, source, env_var,
|
|
52
|
+
default}`` for the API / UI.
|
|
53
|
+
|
|
54
|
+
Built-in defaults mirror the :mod:`supertable.config.settings` dataclass
|
|
55
|
+
defaults, so behaviour is identical whether a value flows through ``settings``
|
|
56
|
+
(no Redis override) or through this resolver.
|
|
57
|
+
"""
|
|
58
|
+
from __future__ import annotations
|
|
59
|
+
|
|
60
|
+
import os
|
|
61
|
+
from dataclasses import dataclass
|
|
62
|
+
from typing import Any, Dict, Optional, Tuple
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Shared auto-pick thresholds: key → (env var, built-in default as string).
|
|
66
|
+
_SHARED_SPEC: Dict[str, Tuple[str, str]] = {
|
|
67
|
+
"engine_lite_max_bytes": ("SUPERTABLE_ENGINE_LITE_MAX_BYTES", str(100 * 1024 * 1024)),
|
|
68
|
+
"engine_spark_min_bytes": ("SUPERTABLE_ENGINE_SPARK_MIN_BYTES", str(10 * 1024 * 1024 * 1024)),
|
|
69
|
+
"engine_freshness_sec": ("SUPERTABLE_ENGINE_FRESHNESS_SEC", "300"),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Per-engine DuckDB runtime pragmas: key → (env var, built-in default as string).
|
|
73
|
+
# Defaults mirror the settings dataclass defaults in supertable/config/settings.py.
|
|
74
|
+
_DUCKDB_SPEC: Dict[str, Tuple[str, str]] = {
|
|
75
|
+
"duckdb_memory_limit": ("SUPERTABLE_DUCKDB_MEMORY_LIMIT", "1GB"),
|
|
76
|
+
"duckdb_io_multiplier": ("SUPERTABLE_DUCKDB_IO_MULTIPLIER", "3"),
|
|
77
|
+
"duckdb_threads": ("SUPERTABLE_DUCKDB_THREADS", ""),
|
|
78
|
+
"duckdb_http_timeout": ("SUPERTABLE_DUCKDB_HTTP_TIMEOUT", ""),
|
|
79
|
+
"duckdb_external_cache_size": ("SUPERTABLE_DUCKDB_EXTERNAL_CACHE_SIZE", "5GB"),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# The two DuckDB engines that each carry their own runtime section.
|
|
83
|
+
DUCKDB_ENGINES: Tuple[str, ...] = ("lite", "pro")
|
|
84
|
+
|
|
85
|
+
# Public whitelists (for callers / catalog / tests).
|
|
86
|
+
SHARED_CONFIG_FIELDS: Tuple[str, ...] = tuple(_SHARED_SPEC.keys())
|
|
87
|
+
DUCKDB_CONFIG_FIELDS: Tuple[str, ...] = tuple(_DUCKDB_SPEC.keys())
|
|
88
|
+
ENGINE_CONFIG_FIELDS: Tuple[str, ...] = SHARED_CONFIG_FIELDS + DUCKDB_CONFIG_FIELDS
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True)
|
|
92
|
+
class EngineRuntimeConfig:
|
|
93
|
+
"""Effective, typed config for one DuckDB engine resolution.
|
|
94
|
+
|
|
95
|
+
Carries the shared auto-pick thresholds (identical across engines) plus the
|
|
96
|
+
DuckDB session pragmas for the engine it was resolved for.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# auto-pick thresholds (shared)
|
|
100
|
+
engine_lite_max_bytes: int
|
|
101
|
+
engine_spark_min_bytes: int
|
|
102
|
+
engine_freshness_sec: int
|
|
103
|
+
|
|
104
|
+
# duckdb session pragmas (per-engine, re-applied live per query)
|
|
105
|
+
duckdb_memory_limit: str # e.g. "1GB"
|
|
106
|
+
duckdb_io_multiplier: float
|
|
107
|
+
duckdb_threads: Optional[int] # None → auto-derive from memory limit
|
|
108
|
+
duckdb_http_timeout: Optional[int] # seconds; None → leave DuckDB default
|
|
109
|
+
duckdb_external_cache_size: str # "" → external file cache disabled
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _redis_cfg(org: str, catalog: Optional[Any]) -> Dict[str, Any]:
|
|
113
|
+
"""Read the stored engine config for ``org`` (best effort).
|
|
114
|
+
|
|
115
|
+
Returns an empty dict when no catalog is supplied or Redis is unreachable,
|
|
116
|
+
so the resolver degrades cleanly to env vars + defaults.
|
|
117
|
+
"""
|
|
118
|
+
if not (org and catalog is not None):
|
|
119
|
+
return {}
|
|
120
|
+
try:
|
|
121
|
+
return catalog.get_engine_config(org) or {}
|
|
122
|
+
except Exception:
|
|
123
|
+
return {}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _effective(stored: Dict[str, Any], key: str, spec: Dict[str, Tuple[str, str]]) -> Tuple[str, str]:
|
|
127
|
+
"""Return ``(value, source)`` for one field. source ∈ redis|env|default.
|
|
128
|
+
|
|
129
|
+
``stored`` is the dict the value is looked up in directly: the top-level
|
|
130
|
+
document for shared fields, or the ``lite`` / ``pro`` sub-section for
|
|
131
|
+
per-engine fields.
|
|
132
|
+
"""
|
|
133
|
+
env_var, default = spec[key]
|
|
134
|
+
redis_val = stored.get(key) if isinstance(stored, dict) else None
|
|
135
|
+
if redis_val is not None and str(redis_val).strip() != "":
|
|
136
|
+
return str(redis_val).strip(), "redis"
|
|
137
|
+
env_val = (os.getenv(env_var) or "").strip()
|
|
138
|
+
if env_val:
|
|
139
|
+
return env_val, "env"
|
|
140
|
+
return default, "default"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _to_int(s: Any, fallback: int) -> int:
|
|
144
|
+
try:
|
|
145
|
+
return int(str(s).strip())
|
|
146
|
+
except (TypeError, ValueError):
|
|
147
|
+
return fallback
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _to_float(s: Any, fallback: float) -> float:
|
|
151
|
+
try:
|
|
152
|
+
return float(str(s).strip())
|
|
153
|
+
except (TypeError, ValueError):
|
|
154
|
+
return fallback
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _build_runtime(redis_cfg: Dict[str, Any], engine: str) -> EngineRuntimeConfig:
|
|
158
|
+
"""Build the typed config for ``engine`` ("lite"/"pro") from a stored doc."""
|
|
159
|
+
section = redis_cfg.get(engine) if isinstance(redis_cfg.get(engine), dict) else {}
|
|
160
|
+
|
|
161
|
+
sv = {k: _effective(redis_cfg, k, _SHARED_SPEC)[0] for k in _SHARED_SPEC}
|
|
162
|
+
dv = {k: _effective(section, k, _DUCKDB_SPEC)[0] for k in _DUCKDB_SPEC}
|
|
163
|
+
|
|
164
|
+
threads = _to_int(dv["duckdb_threads"], 0) if dv["duckdb_threads"] else 0
|
|
165
|
+
http = _to_int(dv["duckdb_http_timeout"], 0) if dv["duckdb_http_timeout"] else 0
|
|
166
|
+
|
|
167
|
+
return EngineRuntimeConfig(
|
|
168
|
+
engine_lite_max_bytes=_to_int(sv["engine_lite_max_bytes"], 100 * 1024 * 1024),
|
|
169
|
+
engine_spark_min_bytes=_to_int(sv["engine_spark_min_bytes"], 10 * 1024 * 1024 * 1024),
|
|
170
|
+
engine_freshness_sec=_to_int(sv["engine_freshness_sec"], 300),
|
|
171
|
+
duckdb_memory_limit=dv["duckdb_memory_limit"].strip() or "1GB",
|
|
172
|
+
duckdb_io_multiplier=_to_float(dv["duckdb_io_multiplier"], 3.0),
|
|
173
|
+
duckdb_threads=(threads if threads > 0 else None),
|
|
174
|
+
duckdb_http_timeout=(http if http > 0 else None),
|
|
175
|
+
duckdb_external_cache_size=dv["duckdb_external_cache_size"].strip(),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def resolve_engine_configs(org: str, catalog: Optional[Any] = None) -> Dict[str, EngineRuntimeConfig]:
|
|
180
|
+
"""Resolve both DuckDB engines in a single Redis read.
|
|
181
|
+
|
|
182
|
+
Returns ``{"lite": EngineRuntimeConfig, "pro": EngineRuntimeConfig}``. The
|
|
183
|
+
shared thresholds are identical in both; only the DuckDB pragmas differ.
|
|
184
|
+
"""
|
|
185
|
+
cfg = _redis_cfg(org, catalog)
|
|
186
|
+
return {e: _build_runtime(cfg, e) for e in DUCKDB_ENGINES}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def resolve_engine_config(org: str, catalog: Optional[Any] = None, engine: str = "lite") -> EngineRuntimeConfig:
|
|
190
|
+
"""Resolve effective config for a single ``engine`` (Redis → env → default).
|
|
191
|
+
|
|
192
|
+
``catalog`` is an optional ``RedisCatalog``. When it is None or Redis is
|
|
193
|
+
unreachable the resolver falls back to environment variables and built-in
|
|
194
|
+
defaults, which keeps the engine usable without Redis (e.g. in unit tests).
|
|
195
|
+
"""
|
|
196
|
+
engine = engine if engine in DUCKDB_ENGINES else "lite"
|
|
197
|
+
return _build_runtime(_redis_cfg(org, catalog), engine)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def resolve_engine_config_provenance(org: str, catalog: Optional[Any] = None) -> Dict[str, Any]:
|
|
201
|
+
"""Per-field provenance for the UI.
|
|
202
|
+
|
|
203
|
+
Shape::
|
|
204
|
+
|
|
205
|
+
{
|
|
206
|
+
"shared": {field: {value, source, env_var, default}, ...},
|
|
207
|
+
"lite": {field: {value, source, env_var, default}, ...},
|
|
208
|
+
"pro": {field: {value, source, env_var, default}, ...},
|
|
209
|
+
"modified_ms": <int|None>,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
``source`` is one of ``redis`` / ``env`` / ``default``.
|
|
213
|
+
"""
|
|
214
|
+
cfg = _redis_cfg(org, catalog)
|
|
215
|
+
|
|
216
|
+
def _block(stored: Dict[str, Any], spec: Dict[str, Tuple[str, str]]) -> Dict[str, Any]:
|
|
217
|
+
out: Dict[str, Any] = {}
|
|
218
|
+
for key, (env_var, default) in spec.items():
|
|
219
|
+
value, source = _effective(stored, key, spec)
|
|
220
|
+
out[key] = {"value": value, "source": source, "env_var": env_var, "default": default}
|
|
221
|
+
return out
|
|
222
|
+
|
|
223
|
+
result: Dict[str, Any] = {"shared": _block(cfg, _SHARED_SPEC)}
|
|
224
|
+
for engine in DUCKDB_ENGINES:
|
|
225
|
+
section = cfg.get(engine) if isinstance(cfg.get(engine), dict) else {}
|
|
226
|
+
result[engine] = _block(section, _DUCKDB_SPEC)
|
|
227
|
+
result["modified_ms"] = cfg.get("modified_ms")
|
|
228
|
+
return result
|
|
@@ -16,7 +16,7 @@ from supertable.utils.sql_parser import SQLParser
|
|
|
16
16
|
from supertable.engine.engine_enum import Engine
|
|
17
17
|
from supertable.engine.duckdb_lite import DuckDBLite
|
|
18
18
|
from supertable.engine.duckdb_pro import DuckDBPro
|
|
19
|
-
from supertable.engine.engine_config import
|
|
19
|
+
from supertable.engine.engine_config import resolve_engine_configs, EngineRuntimeConfig
|
|
20
20
|
from supertable.data_classes import Reflection
|
|
21
21
|
from supertable.config.defaults import logger
|
|
22
22
|
|
|
@@ -143,10 +143,14 @@ class Executor:
|
|
|
143
143
|
log_prefix: str,
|
|
144
144
|
) -> Tuple[pd.DataFrame, str]:
|
|
145
145
|
# Resolve engine config live (Redis → env → default) for this query so
|
|
146
|
-
# UI changes take effect immediately without restart or cache.
|
|
147
|
-
|
|
146
|
+
# UI changes take effect immediately without restart or cache. Lite and
|
|
147
|
+
# Pro carry independent DuckDB pragmas; the shared auto-pick thresholds
|
|
148
|
+
# are identical in both, so either may drive the routing decision.
|
|
149
|
+
cfgs = resolve_engine_configs(self.organization, self._get_catalog())
|
|
150
|
+
lite_cfg = cfgs["lite"]
|
|
151
|
+
pro_cfg = cfgs["pro"]
|
|
148
152
|
|
|
149
|
-
chosen = engine if engine != Engine.AUTO else self._auto_pick(reflection,
|
|
153
|
+
chosen = engine if engine != Engine.AUTO else self._auto_pick(reflection, lite_cfg)
|
|
150
154
|
|
|
151
155
|
def timer_capture(evt: str):
|
|
152
156
|
timer.capture_and_reset_timing(evt)
|
|
@@ -158,7 +162,7 @@ class Executor:
|
|
|
158
162
|
query_manager=query_manager,
|
|
159
163
|
timer_capture=timer_capture,
|
|
160
164
|
log_prefix=log_prefix,
|
|
161
|
-
engine_config=
|
|
165
|
+
engine_config=lite_cfg,
|
|
162
166
|
)
|
|
163
167
|
used = "duckdb_lite"
|
|
164
168
|
|
|
@@ -170,7 +174,7 @@ class Executor:
|
|
|
170
174
|
query_manager=query_manager,
|
|
171
175
|
timer_capture=timer_capture,
|
|
172
176
|
log_prefix=log_prefix,
|
|
173
|
-
engine_config=
|
|
177
|
+
engine_config=pro_cfg,
|
|
174
178
|
)
|
|
175
179
|
used = "duckdb_pro"
|
|
176
180
|
|
|
@@ -25,7 +25,7 @@ from supertable.engine.engine_common import (
|
|
|
25
25
|
escape_parquet_path,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
from urllib.parse import urlparse
|
|
28
|
+
from urllib.parse import urlparse, unquote
|
|
29
29
|
|
|
30
30
|
# Suppress verbose INFO logging from PyHive and Thrift libraries.
|
|
31
31
|
# PyHive logs every SQL statement (CREATE VIEW, DROP VIEW, SET, etc.)
|
|
@@ -77,8 +77,14 @@ def _to_s3a_path(file_path: str) -> str:
|
|
|
77
77
|
|
|
78
78
|
if file_path.startswith("http://") or file_path.startswith("https://"):
|
|
79
79
|
parsed = urlparse(file_path)
|
|
80
|
-
# path is /bucket/key — strip the leading slash, split into bucket + key
|
|
81
|
-
|
|
80
|
+
# path is /bucket/key — strip the leading slash, split into bucket + key.
|
|
81
|
+
# urlparse leaves percent-escapes intact, and boto3 presigning encodes
|
|
82
|
+
# the key (Hive partition dirs like ``year=2006`` become ``year%3D2006``).
|
|
83
|
+
# Spark's S3A client expects the *raw* object key and re-encodes itself,
|
|
84
|
+
# so we must decode here — otherwise Ceph RGW, which matches keys
|
|
85
|
+
# literally, 404s on the ``%3D`` form. The presign query string is
|
|
86
|
+
# dropped on purpose: Spark authenticates with its own fs.s3a.* creds.
|
|
87
|
+
path = unquote(parsed.path).lstrip("/")
|
|
82
88
|
if "/" in path:
|
|
83
89
|
return f"s3a://{path}"
|
|
84
90
|
# Degenerate case: only bucket, no key — return as-is
|
|
@@ -1468,7 +1468,7 @@ return 1
|
|
|
1468
1468
|
return 0
|
|
1469
1469
|
|
|
1470
1470
|
# ========================================================================= #
|
|
1471
|
-
# Spark Thrift cluster management (org-scoped: supertable:{org}:system:
|
|
1471
|
+
# Spark Thrift cluster management (org-scoped: supertable:{org}:system:engine:thrifts)
|
|
1472
1472
|
# ========================================================================= #
|
|
1473
1473
|
|
|
1474
1474
|
def register_spark_cluster(self, org: str, cluster_id: str, config: Dict[str, Any]) -> None:
|
|
@@ -1497,7 +1497,7 @@ return 1
|
|
|
1497
1497
|
doc.setdefault("s3_enabled", True)
|
|
1498
1498
|
doc["modified_ms"] = _now_ms()
|
|
1499
1499
|
self.r.hset(
|
|
1500
|
-
RK.
|
|
1500
|
+
RK.engine_thrifts(org),
|
|
1501
1501
|
cluster_id,
|
|
1502
1502
|
json.dumps(doc, default=str),
|
|
1503
1503
|
)
|
|
@@ -1512,7 +1512,7 @@ return 1
|
|
|
1512
1512
|
if not org:
|
|
1513
1513
|
return []
|
|
1514
1514
|
try:
|
|
1515
|
-
raw = self.r.hgetall(RK.
|
|
1515
|
+
raw = self.r.hgetall(RK.engine_thrifts(org))
|
|
1516
1516
|
clusters = []
|
|
1517
1517
|
for _cid, data in raw.items():
|
|
1518
1518
|
try:
|
|
@@ -1530,7 +1530,7 @@ return 1
|
|
|
1530
1530
|
if not (org and cluster_id):
|
|
1531
1531
|
return False
|
|
1532
1532
|
try:
|
|
1533
|
-
removed = self.r.hdel(RK.
|
|
1533
|
+
removed = self.r.hdel(RK.engine_thrifts(org), cluster_id)
|
|
1534
1534
|
if removed:
|
|
1535
1535
|
logger.info(f"[redis-catalog] spark thrift deleted: {org}/{cluster_id}")
|
|
1536
1536
|
return bool(removed)
|
|
@@ -1578,7 +1578,7 @@ return 1
|
|
|
1578
1578
|
return candidates[0]
|
|
1579
1579
|
|
|
1580
1580
|
# ========================================================================= #
|
|
1581
|
-
# Spark Plug management (org-scoped: supertable:{org}:system:
|
|
1581
|
+
# Spark Plug management (org-scoped: supertable:{org}:system:engine:plugs)
|
|
1582
1582
|
# ========================================================================= #
|
|
1583
1583
|
|
|
1584
1584
|
def register_spark_plug(self, org: str, plug_id: str, config: Dict[str, Any]) -> None:
|
|
@@ -1604,7 +1604,7 @@ return 1
|
|
|
1604
1604
|
doc.setdefault("webui_url", "")
|
|
1605
1605
|
doc["modified_ms"] = _now_ms()
|
|
1606
1606
|
self.r.hset(
|
|
1607
|
-
RK.
|
|
1607
|
+
RK.engine_plugs(org),
|
|
1608
1608
|
plug_id,
|
|
1609
1609
|
json.dumps(doc, default=str),
|
|
1610
1610
|
)
|
|
@@ -1669,45 +1669,53 @@ return 1
|
|
|
1669
1669
|
# Canonical field names and their env-var counterparts. Used by the
|
|
1670
1670
|
# resolver in engine_common to fall back to os.getenv when a field
|
|
1671
1671
|
# is absent from Redis.
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
"
|
|
1675
|
-
"
|
|
1676
|
-
"
|
|
1677
|
-
"
|
|
1678
|
-
"
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
}
|
|
1672
|
+
# Per-engine DuckDB runtime pragmas (separate sections for "lite" / "pro").
|
|
1673
|
+
DUCKDB_CONFIG_FIELDS = (
|
|
1674
|
+
"duckdb_memory_limit",
|
|
1675
|
+
"duckdb_io_multiplier",
|
|
1676
|
+
"duckdb_threads",
|
|
1677
|
+
"duckdb_http_timeout",
|
|
1678
|
+
"duckdb_external_cache_size",
|
|
1679
|
+
)
|
|
1680
|
+
DUCKDB_ENGINES = ("lite", "pro")
|
|
1682
1681
|
|
|
1683
1682
|
def set_engine_config(
|
|
1684
1683
|
self,
|
|
1685
1684
|
org: str,
|
|
1685
|
+
engine: str,
|
|
1686
1686
|
config: Dict[str, Any],
|
|
1687
1687
|
) -> bool:
|
|
1688
|
-
"""Store
|
|
1688
|
+
"""Store DuckDB runtime configuration for one engine ("lite" / "pro").
|
|
1689
1689
|
|
|
1690
|
-
Org-level system scope: one engine
|
|
1690
|
+
Org-level system scope: one engine document per organization, applied
|
|
1691
1691
|
globally across all supertables (not per-supertable).
|
|
1692
1692
|
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1693
|
+
Only the requested engine's section is replaced; the other engine's
|
|
1694
|
+
section and the shared auto-pick thresholds are preserved. Within the
|
|
1695
|
+
section it is a full replace (last-write-wins) — omitting a field clears
|
|
1696
|
+
its Redis override so it falls back to env / default at read time.
|
|
1696
1697
|
|
|
1697
|
-
|
|
1698
|
+
Only recognised DuckDB fields (see DUCKDB_CONFIG_FIELDS) are persisted —
|
|
1699
|
+
unknown keys are silently dropped to prevent injection of arbitrary
|
|
1700
|
+
settings.
|
|
1698
1701
|
"""
|
|
1699
1702
|
if not org:
|
|
1700
1703
|
return False
|
|
1704
|
+
engine = (engine or "").strip().lower()
|
|
1705
|
+
if engine not in self.DUCKDB_ENGINES:
|
|
1706
|
+
return False
|
|
1701
1707
|
try:
|
|
1702
|
-
|
|
1703
|
-
|
|
1708
|
+
doc = self.get_engine_config(org) or {}
|
|
1709
|
+
# Whitelist recognised DuckDB fields only for this engine section.
|
|
1710
|
+
section = {
|
|
1704
1711
|
k: config[k]
|
|
1705
|
-
for k in self.
|
|
1712
|
+
for k in self.DUCKDB_CONFIG_FIELDS
|
|
1706
1713
|
if k in config and config[k] is not None and str(config[k]).strip() != ""
|
|
1707
1714
|
}
|
|
1715
|
+
doc[engine] = section
|
|
1708
1716
|
doc["modified_ms"] = _now_ms()
|
|
1709
1717
|
self.r.set(
|
|
1710
|
-
RK.
|
|
1718
|
+
RK.engine_duckdb(org),
|
|
1711
1719
|
json.dumps(doc, default=str),
|
|
1712
1720
|
)
|
|
1713
1721
|
return True
|
|
@@ -1726,7 +1734,7 @@ return 1
|
|
|
1726
1734
|
if not org:
|
|
1727
1735
|
return None
|
|
1728
1736
|
try:
|
|
1729
|
-
raw = self.r.get(RK.
|
|
1737
|
+
raw = self.r.get(RK.engine_duckdb(org))
|
|
1730
1738
|
if raw:
|
|
1731
1739
|
return json.loads(raw)
|
|
1732
1740
|
except (redis.RedisError, json.JSONDecodeError) as e:
|
|
@@ -35,10 +35,10 @@ Hierarchy (v2)
|
|
|
35
35
|
shares:
|
|
36
36
|
doc:{share_id} STRING share definition
|
|
37
37
|
index SET share IDs
|
|
38
|
-
|
|
38
|
+
engine:
|
|
39
39
|
thrifts HASH Spark Thrift clusters
|
|
40
40
|
plugs HASH Spark Plug runtimes
|
|
41
|
-
|
|
41
|
+
duckdb STRING DuckDB runtime config
|
|
42
42
|
lakes: ← user-data sentinel
|
|
43
43
|
{sup}: ← supertable scope
|
|
44
44
|
meta:
|
|
@@ -121,7 +121,7 @@ SUPERTABLE_PREFIX: str = "supertable"
|
|
|
121
121
|
DATAISLAND_PREFIX: str = "dataisland"
|
|
122
122
|
|
|
123
123
|
# Position-2 sentinel under ``supertable:{org}:`` for org-level platform
|
|
124
|
-
# state (auth tokens, audit, shares,
|
|
124
|
+
# state (auth tokens, audit, shares, engine).
|
|
125
125
|
SYSTEM_SCOPE: str = "system"
|
|
126
126
|
|
|
127
127
|
# Position-2 sentinel under ``supertable:{org}:`` for user-supplied
|
|
@@ -394,16 +394,21 @@ def share_index(org: str) -> str:
|
|
|
394
394
|
return f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{SYSTEM_SCOPE}:shares:index"
|
|
395
395
|
|
|
396
396
|
|
|
397
|
-
# --- Org-level
|
|
397
|
+
# --- Org-level Engine ------------------------------------------------------ #
|
|
398
|
+
#
|
|
399
|
+
# All query-engine state lives under one ``system:engine:`` namespace: the
|
|
400
|
+
# Spark Thrift cluster registry, the Spark Plug runtime registry, and the
|
|
401
|
+
# DuckDB (Lite/Pro) runtime config document. Grouping them keeps every engine
|
|
402
|
+
# knob the UI exposes under a single, discoverable path.
|
|
398
403
|
|
|
399
|
-
def
|
|
404
|
+
def engine_thrifts(org: str) -> str:
|
|
400
405
|
"""Spark Thrift cluster registry (HASH)."""
|
|
401
|
-
return f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{SYSTEM_SCOPE}:
|
|
406
|
+
return f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{SYSTEM_SCOPE}:engine:thrifts"
|
|
402
407
|
|
|
403
408
|
|
|
404
|
-
def
|
|
409
|
+
def engine_plugs(org: str) -> str:
|
|
405
410
|
"""Spark Plug runtime registry (HASH)."""
|
|
406
|
-
return f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{SYSTEM_SCOPE}:
|
|
411
|
+
return f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{SYSTEM_SCOPE}:engine:plugs"
|
|
407
412
|
|
|
408
413
|
|
|
409
414
|
# =========================================================================== #
|
|
@@ -570,11 +575,15 @@ def pipe_pattern(org: str, sup: str, staging_name: str) -> str:
|
|
|
570
575
|
)
|
|
571
576
|
|
|
572
577
|
|
|
573
|
-
# ---
|
|
578
|
+
# --- DuckDB engine config -------------------------------------------------- #
|
|
579
|
+
|
|
580
|
+
def engine_duckdb(org: str) -> str:
|
|
581
|
+
"""DuckDB (Lite/Pro) runtime config (STRING). Org-level system scope (global, not per-supertable).
|
|
574
582
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
583
|
+
One document holds both engines' DuckDB pragmas plus the shared auto-pick
|
|
584
|
+
thresholds; see :mod:`supertable.engine.engine_config`.
|
|
585
|
+
"""
|
|
586
|
+
return f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{SYSTEM_SCOPE}:engine:duckdb"
|
|
578
587
|
|
|
579
588
|
|
|
580
589
|
# --- Locks ----------------------------------------------------------------- #
|
|
@@ -68,9 +68,9 @@ def _all_helpers() -> list[tuple[str, str, str]]:
|
|
|
68
68
|
("audit_legal_hold", RK.audit_legal_hold(ORG), f"{sys_pre}:audit:legal_hold"),
|
|
69
69
|
("share_doc", RK.share_doc(ORG, SHARE), f"{sys_pre}:shares:doc:{SHARE}"),
|
|
70
70
|
("share_index", RK.share_index(ORG), f"{sys_pre}:shares:index"),
|
|
71
|
-
("
|
|
72
|
-
("
|
|
73
|
-
("
|
|
71
|
+
("engine_thrifts", RK.engine_thrifts(ORG), f"{sys_pre}:engine:thrifts"),
|
|
72
|
+
("engine_plugs", RK.engine_plugs(ORG), f"{sys_pre}:engine:plugs"),
|
|
73
|
+
("engine_duckdb", RK.engine_duckdb(ORG), f"{sys_pre}:engine:duckdb"),
|
|
74
74
|
|
|
75
75
|
# ---- Lakes scope (per-org supertable enumeration) ---------------
|
|
76
76
|
("lakes_scope", RK.lakes_scope(ORG), f"supertable:{ORG}:lakes"),
|