supertable 2.2.4__tar.gz → 2.2.5__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.
Files changed (178) hide show
  1. {supertable-2.2.4/supertable.egg-info → supertable-2.2.5}/PKG-INFO +1 -1
  2. {supertable-2.2.4 → supertable-2.2.5}/pyproject.toml +1 -1
  3. {supertable-2.2.4 → supertable-2.2.5}/setup.py +1 -1
  4. {supertable-2.2.4 → supertable-2.2.5}/supertable/__init__.py +1 -1
  5. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/duckdb_lite.py +6 -0
  6. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/duckdb_pro.py +6 -0
  7. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/engine_common.py +58 -3
  8. supertable-2.2.5/supertable/engine/engine_config.py +153 -0
  9. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/executor.py +28 -18
  10. {supertable-2.2.4 → supertable-2.2.5}/supertable/redis_catalog.py +23 -8
  11. {supertable-2.2.4 → supertable-2.2.5}/supertable/redis_keys.py +4 -7
  12. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_redis_key_prefix.py +1 -3
  13. {supertable-2.2.4 → supertable-2.2.5/supertable.egg-info}/PKG-INFO +1 -1
  14. {supertable-2.2.4 → supertable-2.2.5}/supertable.egg-info/SOURCES.txt +1 -0
  15. {supertable-2.2.4 → supertable-2.2.5}/LICENSE +0 -0
  16. {supertable-2.2.4 → supertable-2.2.5}/README.md +0 -0
  17. {supertable-2.2.4 → supertable-2.2.5}/requirements.txt +0 -0
  18. {supertable-2.2.4 → supertable-2.2.5}/setup.cfg +0 -0
  19. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/__init__.py +0 -0
  20. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/admin.py +0 -0
  21. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/chain.py +0 -0
  22. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/consumers.py +0 -0
  23. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/crypto.py +0 -0
  24. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/events.py +0 -0
  25. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/export.py +0 -0
  26. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/logger.py +0 -0
  27. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/middleware.py +0 -0
  28. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/reader.py +0 -0
  29. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/retention.py +0 -0
  30. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/tests/__init__.py +0 -0
  31. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/tests/test_chain.py +0 -0
  32. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/tests/test_crypto.py +0 -0
  33. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/tests/test_emit.py +0 -0
  34. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/tests/test_events.py +0 -0
  35. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/tests/test_retention.py +0 -0
  36. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/writer_parquet.py +0 -0
  37. {supertable-2.2.4 → supertable-2.2.5}/supertable/audit/writer_redis.py +0 -0
  38. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/__init__.py +0 -0
  39. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/defaults.py +0 -0
  40. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/homedir.py +0 -0
  41. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/settings.py +0 -0
  42. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/tests/__init__.py +0 -0
  43. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/tests/test_defaults.py +0 -0
  44. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/tests/test_homedir.py +0 -0
  45. {supertable-2.2.4 → supertable-2.2.5}/supertable/config/tests/test_settings.py +0 -0
  46. {supertable-2.2.4 → supertable-2.2.5}/supertable/data_classes.py +0 -0
  47. {supertable-2.2.4 → supertable-2.2.5}/supertable/data_reader.py +0 -0
  48. {supertable-2.2.4 → supertable-2.2.5}/supertable/data_writer.py +0 -0
  49. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/__init__.py +0 -0
  50. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/__init__.py +0 -0
  51. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/__main__.py +0 -0
  52. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/check_filter_builder.py +0 -0
  53. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/controller.py +0 -0
  54. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/data_writer_helpers.py +0 -0
  55. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/defaults.py +0 -0
  56. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/dummy_data.py +0 -0
  57. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/read_parquet_header.py +0 -0
  58. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s01_01_01_create_super_table.py +0 -0
  59. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s01_01_02_enable_mirroring_formats.py +0 -0
  60. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s01_02_create_roles.py +0 -0
  61. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s01_03_create_users.py +0 -0
  62. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s02_01_write_dummy_data.py +0 -0
  63. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s02_02_write_single_data.py +0 -0
  64. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s02_03_01_write_staging.py +0 -0
  65. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s02_03_02_create_pipe.py +0 -0
  66. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s02_04_01_write_monitoring_simple.py +0 -0
  67. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s02_04_02_write_monitoring_parallel.py +0 -0
  68. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s02_05_write_tombstone.py +0 -0
  69. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_01_read_data_error.py +0 -0
  70. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_02_01_read_super_data_ok.py +0 -0
  71. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_02_02_read_table_data_ok.py +0 -0
  72. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_03_read_meta.py +0 -0
  73. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_04_read_staging.py +0 -0
  74. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_06_01_read_roles.py +0 -0
  75. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_06_02_read_user.py +0 -0
  76. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_07_01_estimate_read.py +0 -0
  77. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_07_02_estimate_files.py +0 -0
  78. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s03_08_read_snapshot_history.py +0 -0
  79. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s04_01_03_delete_pipe.py +0 -0
  80. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s05_01_delete_table.py +0 -0
  81. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/quickstart/s05_02_delete_super_table.py +0 -0
  82. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/webshop/__init__.py +0 -0
  83. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/webshop/core.py +0 -0
  84. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/webshop/defaults.py +0 -0
  85. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/webshop/generate.py +0 -0
  86. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/webshop/load.py +0 -0
  87. {supertable-2.2.4 → supertable-2.2.5}/supertable/demo/webshop/topup.py +0 -0
  88. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/__init__.py +0 -0
  89. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/data_estimator.py +0 -0
  90. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/engine_enum.py +0 -0
  91. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/plan_stats.py +0 -0
  92. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/spark_thrift.py +0 -0
  93. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/tests/__init__.py +0 -0
  94. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/tests/conftest.py +0 -0
  95. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/tests/test_dedup_read.py +0 -0
  96. {supertable-2.2.4 → supertable-2.2.5}/supertable/engine/tests/test_engine.py +0 -0
  97. {supertable-2.2.4 → supertable-2.2.5}/supertable/errors.py +0 -0
  98. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/__init__.py +0 -0
  99. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/benchmarks/__init__.py +0 -0
  100. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/benchmarks/benchmark_locking.py +0 -0
  101. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/benchmarks/measure_lock_speed.py +0 -0
  102. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/benchmarks/measure_lock_time.py +0 -0
  103. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/file_lock.py +0 -0
  104. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/redis_lock.py +0 -0
  105. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/tests/__init__.py +0 -0
  106. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/tests/test_file_lock.py +0 -0
  107. {supertable-2.2.4 → supertable-2.2.5}/supertable/locking/tests/test_redis_lock.py +0 -0
  108. {supertable-2.2.4 → supertable-2.2.5}/supertable/logging.py +0 -0
  109. {supertable-2.2.4 → supertable-2.2.5}/supertable/meta_reader.py +0 -0
  110. {supertable-2.2.4 → supertable-2.2.5}/supertable/mirroring/__init__.py +0 -0
  111. {supertable-2.2.4 → supertable-2.2.5}/supertable/mirroring/mirror_delta.py +0 -0
  112. {supertable-2.2.4 → supertable-2.2.5}/supertable/mirroring/mirror_formats.py +0 -0
  113. {supertable-2.2.4 → supertable-2.2.5}/supertable/mirroring/mirror_iceberg.py +0 -0
  114. {supertable-2.2.4 → supertable-2.2.5}/supertable/mirroring/mirror_parquet.py +0 -0
  115. {supertable-2.2.4 → supertable-2.2.5}/supertable/monitoring/__init__.py +0 -0
  116. {supertable-2.2.4 → supertable-2.2.5}/supertable/monitoring/partitions.py +0 -0
  117. {supertable-2.2.4 → supertable-2.2.5}/supertable/monitoring_writer.py +0 -0
  118. {supertable-2.2.4 → supertable-2.2.5}/supertable/plan_extender.py +0 -0
  119. {supertable-2.2.4 → supertable-2.2.5}/supertable/processing.py +0 -0
  120. {supertable-2.2.4 → supertable-2.2.5}/supertable/query_plan_manager.py +0 -0
  121. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/__init__.py +0 -0
  122. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/access_control.py +0 -0
  123. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/filter_builder.py +0 -0
  124. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/permissions.py +0 -0
  125. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/role_manager.py +0 -0
  126. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/row_column_security.py +0 -0
  127. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/tests/test_filter_builder.py +0 -0
  128. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/tests/test_rbac.py +0 -0
  129. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/tests/test_rbac_per_table.py +0 -0
  130. {supertable-2.2.4 → supertable-2.2.5}/supertable/rbac/user_manager.py +0 -0
  131. {supertable-2.2.4 → supertable-2.2.5}/supertable/redis_connector.py +0 -0
  132. {supertable-2.2.4 → supertable-2.2.5}/supertable/redis_infra.py +0 -0
  133. {supertable-2.2.4 → supertable-2.2.5}/supertable/simple_table.py +0 -0
  134. {supertable-2.2.4 → supertable-2.2.5}/supertable/staging_area.py +0 -0
  135. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/__init__.py +0 -0
  136. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/azure_storage.py +0 -0
  137. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/gcp_storage.py +0 -0
  138. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/local_storage.py +0 -0
  139. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/minio_storage.py +0 -0
  140. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/s3_storage.py +0 -0
  141. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/storage_factory.py +0 -0
  142. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/storage_interface.py +0 -0
  143. {supertable-2.2.4 → supertable-2.2.5}/supertable/storage/tests/test_storage.py +0 -0
  144. {supertable-2.2.4 → supertable-2.2.5}/supertable/super_pipe.py +0 -0
  145. {supertable-2.2.4 → supertable-2.2.5}/supertable/super_table.py +0 -0
  146. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/__init__.py +0 -0
  147. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_align_to_schema_fix.py +0 -0
  148. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_create_if_missing.py +0 -0
  149. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_data_reader.py +0 -0
  150. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_data_reader_preflight.py +0 -0
  151. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_data_writer.py +0 -0
  152. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_data_writer_compact.py +0 -0
  153. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_data_writer_comprehensive.py +0 -0
  154. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_data_writer_tombstones.py +0 -0
  155. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_dedup_on_read_write.py +0 -0
  156. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_errors.py +0 -0
  157. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_meta_reader.py +0 -0
  158. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_monitoring_partitions.py +0 -0
  159. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_monitoring_sink_guard.py +0 -0
  160. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_newer_than.py +0 -0
  161. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_process_delete_only.py +0 -0
  162. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_processing.py +0 -0
  163. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_processing_compact_resources.py +0 -0
  164. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_query_sql.py +0 -0
  165. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_simple_table.py +0 -0
  166. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_small_file_compaction.py +0 -0
  167. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_super_table.py +0 -0
  168. {supertable-2.2.4 → supertable-2.2.5}/supertable/tests/test_supertable_all.py +0 -0
  169. {supertable-2.2.4 → supertable-2.2.5}/supertable/utils/__init__.py +0 -0
  170. {supertable-2.2.4 → supertable-2.2.5}/supertable/utils/helper.py +0 -0
  171. {supertable-2.2.4 → supertable-2.2.5}/supertable/utils/profiler.py +0 -0
  172. {supertable-2.2.4 → supertable-2.2.5}/supertable/utils/sql_parser.py +0 -0
  173. {supertable-2.2.4 → supertable-2.2.5}/supertable/utils/tests/test_sql_parser_columns.py +0 -0
  174. {supertable-2.2.4 → supertable-2.2.5}/supertable/utils/timer.py +0 -0
  175. {supertable-2.2.4 → supertable-2.2.5}/supertable.egg-info/dependency_links.txt +0 -0
  176. {supertable-2.2.4 → supertable-2.2.5}/supertable.egg-info/entry_points.txt +0 -0
  177. {supertable-2.2.4 → supertable-2.2.5}/supertable.egg-info/requires.txt +0 -0
  178. {supertable-2.2.4 → supertable-2.2.5}/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.2.4
3
+ Version: 2.2.5
4
4
  Summary: SuperTable — versioned data lake library for SQL analytics on Parquet + Redis.
5
5
  Author: Levente Kupas
6
6
  Author-email: Levente Kupas <lkupas@kladnasoft.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "supertable"
7
- version = "2.2.4"
7
+ version = "2.2.5"
8
8
  description = "SuperTable — versioned data lake library for SQL analytics on Parquet + Redis."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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.4",
22
+ version="2.2.5",
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.4"
28
+ __version__ = "2.2.5"
29
29
 
30
30
  # Re-export the core public surface so users can do ``from supertable import …``
31
31
  # instead of remembering submodule paths.
@@ -21,6 +21,7 @@ from supertable.engine.engine_common import (
21
21
  create_reflection_view_with_presign_retry,
22
22
  rewrite_query_with_hashed_tables,
23
23
  init_connection,
24
+ apply_runtime_pragmas,
24
25
  create_rbac_view,
25
26
  create_dedup_view,
26
27
  create_tombstone_view,
@@ -105,6 +106,7 @@ class DuckDBLite:
105
106
  query_manager: QueryPlanManager,
106
107
  timer_capture,
107
108
  log_prefix: str = "",
109
+ engine_config=None,
108
110
  ) -> pd.DataFrame:
109
111
  tried_presign = False
110
112
 
@@ -237,6 +239,10 @@ class DuckDBLite:
237
239
  except Exception:
238
240
  pass
239
241
 
242
+ # Re-apply live engine config (memory/threads/http/cache) so UI
243
+ # changes take effect on this persistent connection per query.
244
+ apply_runtime_pragmas(con, engine_config)
245
+
240
246
  logger.debug(f"{log_prefix}[duckdb.lite] executing: {executing_query}")
241
247
  result = con.execute(executing_query).fetchdf()
242
248
 
@@ -25,6 +25,7 @@ from supertable.engine.engine_common import (
25
25
  make_presigned_list,
26
26
  rewrite_query_with_hashed_tables,
27
27
  init_connection,
28
+ apply_runtime_pragmas,
28
29
  create_rbac_view,
29
30
  create_dedup_view,
30
31
  create_tombstone_view,
@@ -268,6 +269,7 @@ class DuckDBPro:
268
269
  query_manager: QueryPlanManager,
269
270
  timer_capture,
270
271
  log_prefix: str = "",
272
+ engine_config=None,
271
273
  ) -> pd.DataFrame:
272
274
  tables_used: Set[str] = set()
273
275
 
@@ -368,6 +370,10 @@ class DuckDBPro:
368
370
 
369
371
  logger.debug(f"{log_prefix}[duckdb.pro] executing: {executing_query}")
370
372
 
373
+ # Re-apply live engine config (memory/threads/http/cache) so UI
374
+ # changes take effect on this persistent connection per query.
375
+ apply_runtime_pragmas(con, engine_config)
376
+
371
377
  # Profiling PRAGMAs are connection-level state. Under concurrent
372
378
  # queries the last SET wins — one query's profile may land in the
373
379
  # wrong file. This is acceptable: profiling is best-effort
@@ -140,7 +140,11 @@ def _parse_memory_limit_mb(value: str) -> Optional[int]:
140
140
  return int(amount)
141
141
 
142
142
 
143
- def _derive_thread_count(memory_limit_str: str, fallback: int = 2) -> int:
143
+ def _derive_thread_count(
144
+ memory_limit_str: str,
145
+ fallback: int = 2,
146
+ multiplier: Optional[float] = None,
147
+ ) -> int:
144
148
  """Derive a safe DuckDB thread count for remote parquet reads.
145
149
 
146
150
  DuckDB uses synchronous IO: each thread can make at most one HTTP/S3
@@ -170,9 +174,10 @@ def _derive_thread_count(memory_limit_str: str, fallback: int = 2) -> int:
170
174
  return fallback
171
175
 
172
176
  cpu_count = _os.cpu_count() or fallback
173
- multiplier = settings.SUPERTABLE_DUCKDB_IO_MULTIPLIER
177
+ if multiplier is None:
178
+ multiplier = settings.SUPERTABLE_DUCKDB_IO_MULTIPLIER
174
179
 
175
- io_threads = cpu_count * multiplier
180
+ io_threads = int(cpu_count * multiplier)
176
181
  # Safety ceiling: never allocate fewer than ~400 MB per thread.
177
182
  memory_floor = max(1, mb // 400)
178
183
  result = min(io_threads, memory_floor)
@@ -625,6 +630,56 @@ def init_connection(
625
630
  pass
626
631
 
627
632
 
633
+ def apply_runtime_pragmas(con: duckdb.DuckDBPyConnection, cfg) -> None:
634
+ """Re-apply the session-settable DuckDB pragmas from a live engine config.
635
+
636
+ DuckDB Lite/Pro reuse a persistent connection, so settings applied once at
637
+ ``init_connection`` time would otherwise freeze for the connection's life.
638
+ Calling this immediately before each query makes the org's engine config
639
+ (memory limit, thread count, HTTP timeout, external file cache) take effect
640
+ live — the connection adopts the latest values without being torn down.
641
+
642
+ ``cfg`` is an ``EngineRuntimeConfig``. When None the connection keeps
643
+ whatever ``init_connection`` applied (settings-based), so callers without a
644
+ resolved config are unaffected. Every pragma is best-effort: httpfs-only
645
+ settings silently no-op on connections that have not loaded httpfs.
646
+ """
647
+ if cfg is None:
648
+ return
649
+
650
+ memory_limit = (cfg.duckdb_memory_limit or "").strip() or "1GB"
651
+ try:
652
+ con.execute(f"PRAGMA memory_limit='{memory_limit}';")
653
+ except Exception:
654
+ pass
655
+
656
+ # Explicit thread count wins; otherwise derive from the live memory limit
657
+ # and IO multiplier (same formula as init_connection).
658
+ if cfg.duckdb_threads is not None:
659
+ thread_count = cfg.duckdb_threads
660
+ else:
661
+ thread_count = _derive_thread_count(memory_limit, multiplier=cfg.duckdb_io_multiplier)
662
+ try:
663
+ con.execute(f"SET threads={thread_count};")
664
+ except Exception:
665
+ pass
666
+
667
+ # httpfs settings — only effective once httpfs is loaded (remote reads).
668
+ if cfg.duckdb_http_timeout is not None:
669
+ try:
670
+ con.execute(f"SET http_timeout={int(cfg.duckdb_http_timeout)};")
671
+ except Exception:
672
+ pass
673
+
674
+ cache_size = (cfg.duckdb_external_cache_size or "").strip()
675
+ if cache_size:
676
+ try:
677
+ con.execute("SET enable_external_file_cache=true;")
678
+ con.execute(f"SET external_file_cache_max_size='{sanitize_sql_string(cache_size)}';")
679
+ except Exception:
680
+ pass
681
+
682
+
628
683
  # =========================================================
629
684
  # RBAC view creation
630
685
  # =========================================================
@@ -0,0 +1,153 @@
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:config:engine``). 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
+ Resolution precedence, per field::
12
+
13
+ Redis (org system config) → environment variable → built-in default
14
+
15
+ Two entry points share that precedence so the engine and the UI never disagree:
16
+
17
+ * :func:`resolve_engine_config` → typed :class:`EngineRuntimeConfig`,
18
+ consumed by the executor and the
19
+ DuckDB leaf executors.
20
+ * :func:`resolve_engine_config_provenance` → per-field ``{value, source,
21
+ env_var, default}`` dict for the
22
+ API / UI to display where each
23
+ effective value came from.
24
+
25
+ Built-in defaults mirror the :mod:`supertable.config.settings` dataclass
26
+ defaults, so behaviour is identical whether a value flows through ``settings``
27
+ (no Redis override) or through this resolver.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import os
32
+ from dataclasses import dataclass
33
+ from typing import Any, Dict, Optional, Tuple
34
+
35
+
36
+ # Canonical field spec: key → (env var, built-in default as string).
37
+ # Defaults mirror the settings dataclass defaults in
38
+ # supertable/config/settings.py — keep the two in sync.
39
+ _FIELD_SPEC: Dict[str, Tuple[str, str]] = {
40
+ "engine_lite_max_bytes": ("SUPERTABLE_ENGINE_LITE_MAX_BYTES", str(100 * 1024 * 1024)),
41
+ "engine_spark_min_bytes": ("SUPERTABLE_ENGINE_SPARK_MIN_BYTES", str(10 * 1024 * 1024 * 1024)),
42
+ "engine_freshness_sec": ("SUPERTABLE_ENGINE_FRESHNESS_SEC", "300"),
43
+ "duckdb_memory_limit": ("SUPERTABLE_DUCKDB_MEMORY_LIMIT", "1GB"),
44
+ "duckdb_io_multiplier": ("SUPERTABLE_DUCKDB_IO_MULTIPLIER", "3"),
45
+ "duckdb_threads": ("SUPERTABLE_DUCKDB_THREADS", ""),
46
+ "duckdb_http_timeout": ("SUPERTABLE_DUCKDB_HTTP_TIMEOUT", ""),
47
+ "duckdb_external_cache_size": ("SUPERTABLE_DUCKDB_EXTERNAL_CACHE_SIZE", "5GB"),
48
+ }
49
+
50
+ # Public tuple of recognised field names (whitelist for callers/tests).
51
+ ENGINE_CONFIG_FIELDS: Tuple[str, ...] = tuple(_FIELD_SPEC.keys())
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class EngineRuntimeConfig:
56
+ """Effective, typed engine configuration for a single resolution."""
57
+
58
+ # auto-pick thresholds
59
+ engine_lite_max_bytes: int
60
+ engine_spark_min_bytes: int
61
+ engine_freshness_sec: int
62
+
63
+ # duckdb session pragmas (re-applied live per query)
64
+ duckdb_memory_limit: str # e.g. "1GB"
65
+ duckdb_io_multiplier: float
66
+ duckdb_threads: Optional[int] # None → auto-derive from memory limit
67
+ duckdb_http_timeout: Optional[int] # seconds; None → leave DuckDB default
68
+ duckdb_external_cache_size: str # "" → external file cache disabled
69
+
70
+
71
+ def _redis_cfg(org: str, catalog: Optional[Any]) -> Dict[str, Any]:
72
+ """Read the stored engine config for ``org`` (best effort).
73
+
74
+ Returns an empty dict when no catalog is supplied or Redis is unreachable,
75
+ so the resolver degrades cleanly to env vars + defaults.
76
+ """
77
+ if not (org and catalog is not None):
78
+ return {}
79
+ try:
80
+ return catalog.get_engine_config(org) or {}
81
+ except Exception:
82
+ return {}
83
+
84
+
85
+ def _effective(redis_cfg: Dict[str, Any], key: str) -> Tuple[str, str]:
86
+ """Return ``(value, source)`` for one field. source ∈ redis|env|default."""
87
+ env_var, default = _FIELD_SPEC[key]
88
+ redis_val = redis_cfg.get(key)
89
+ if redis_val is not None and str(redis_val).strip() != "":
90
+ return str(redis_val).strip(), "redis"
91
+ env_val = (os.getenv(env_var) or "").strip()
92
+ if env_val:
93
+ return env_val, "env"
94
+ return default, "default"
95
+
96
+
97
+ def _to_int(s: Any, fallback: int) -> int:
98
+ try:
99
+ return int(str(s).strip())
100
+ except (TypeError, ValueError):
101
+ return fallback
102
+
103
+
104
+ def _to_float(s: Any, fallback: float) -> float:
105
+ try:
106
+ return float(str(s).strip())
107
+ except (TypeError, ValueError):
108
+ return fallback
109
+
110
+
111
+ def resolve_engine_config(org: str, catalog: Optional[Any] = None) -> EngineRuntimeConfig:
112
+ """Resolve effective engine config for ``org`` (Redis → env → default).
113
+
114
+ ``catalog`` is an optional ``RedisCatalog``. When it is None or Redis is
115
+ unreachable the resolver falls back to environment variables and built-in
116
+ defaults, which keeps the engine usable without Redis (e.g. in unit tests).
117
+ """
118
+ cfg = _redis_cfg(org, catalog)
119
+ val = {k: _effective(cfg, k)[0] for k in _FIELD_SPEC}
120
+
121
+ threads = _to_int(val["duckdb_threads"], 0) if val["duckdb_threads"] else 0
122
+ http = _to_int(val["duckdb_http_timeout"], 0) if val["duckdb_http_timeout"] else 0
123
+
124
+ return EngineRuntimeConfig(
125
+ engine_lite_max_bytes=_to_int(val["engine_lite_max_bytes"], 100 * 1024 * 1024),
126
+ engine_spark_min_bytes=_to_int(val["engine_spark_min_bytes"], 10 * 1024 * 1024 * 1024),
127
+ engine_freshness_sec=_to_int(val["engine_freshness_sec"], 300),
128
+ duckdb_memory_limit=val["duckdb_memory_limit"].strip() or "1GB",
129
+ duckdb_io_multiplier=_to_float(val["duckdb_io_multiplier"], 3.0),
130
+ duckdb_threads=(threads if threads > 0 else None),
131
+ duckdb_http_timeout=(http if http > 0 else None),
132
+ duckdb_external_cache_size=val["duckdb_external_cache_size"].strip(),
133
+ )
134
+
135
+
136
+ def resolve_engine_config_provenance(org: str, catalog: Optional[Any] = None) -> Dict[str, Any]:
137
+ """Per-field provenance for the UI: ``{value, source, env_var, default}``.
138
+
139
+ ``source`` is one of ``redis`` / ``env`` / ``default``. The extra
140
+ ``modified_ms`` key carries the last-write timestamp (or None).
141
+ """
142
+ cfg = _redis_cfg(org, catalog)
143
+ result: Dict[str, Any] = {}
144
+ for key, (env_var, default) in _FIELD_SPEC.items():
145
+ value, source = _effective(cfg, key)
146
+ result[key] = {
147
+ "value": value,
148
+ "source": source,
149
+ "env_var": env_var,
150
+ "default": default,
151
+ }
152
+ result["modified_ms"] = cfg.get("modified_ms")
153
+ return result
@@ -16,9 +16,9 @@ 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 resolve_engine_config, EngineRuntimeConfig
19
20
  from supertable.data_classes import Reflection
20
21
  from supertable.config.defaults import logger
21
- from supertable.config.settings import settings
22
22
 
23
23
 
24
24
  # Module-level singleton for the pro executor so the persistent
@@ -46,8 +46,23 @@ class Executor:
46
46
  self.organization = organization
47
47
  self.lite_exec = DuckDBLite(storage=storage)
48
48
  self.spark_exec = None
49
+ self._catalog = None # lazily created RedisCatalog for live config reads
49
50
 
50
- def _auto_pick(self, reflection: Reflection) -> Engine:
51
+ def _get_catalog(self):
52
+ """Lazily create a RedisCatalog for live engine-config reads.
53
+
54
+ Returns None when Redis is unreachable so config resolution degrades to
55
+ environment variables and built-in defaults instead of failing a query.
56
+ """
57
+ if self._catalog is None:
58
+ try:
59
+ from supertable.redis_catalog import RedisCatalog
60
+ self._catalog = RedisCatalog()
61
+ except Exception:
62
+ self._catalog = False # sentinel: construction failed, do not retry
63
+ return self._catalog or None
64
+
65
+ def _auto_pick(self, reflection: Reflection, cfg: EngineRuntimeConfig) -> Engine:
51
66
  """Select the best engine based on data size and freshness.
52
67
 
53
68
  Decision matrix (all thresholds configurable via env vars):
@@ -74,21 +89,10 @@ class Executor:
74
89
  """
75
90
  bytes_total = reflection.reflection_bytes
76
91
 
77
- # --- thresholds (env-configurable) ---
78
- try:
79
- lite_max = settings.SUPERTABLE_ENGINE_LITE_MAX_BYTES
80
- except ValueError:
81
- lite_max = 100 * 1024 * 1024 # 100 MB
82
-
83
- try:
84
- spark_min = settings.SUPERTABLE_ENGINE_SPARK_MIN_BYTES
85
- except ValueError:
86
- spark_min = 10 * 1024 * 1024 * 1024 # 10 GB
87
-
88
- try:
89
- freshness_threshold_s = settings.SUPERTABLE_ENGINE_FRESHNESS_SEC
90
- except ValueError:
91
- freshness_threshold_s = 300 # 5 min
92
+ # --- thresholds (resolved live from org system config) ---
93
+ lite_max = cfg.engine_lite_max_bytes
94
+ spark_min = cfg.engine_spark_min_bytes
95
+ freshness_threshold_s = cfg.engine_freshness_sec
92
96
 
93
97
  # --- freshness: how long ago was the most recent snapshot updated ---
94
98
  if reflection.freshness_ms > 0:
@@ -138,7 +142,11 @@ class Executor:
138
142
  plan_stats: PlanStats,
139
143
  log_prefix: str,
140
144
  ) -> Tuple[pd.DataFrame, str]:
141
- chosen = engine if engine != Engine.AUTO else self._auto_pick(reflection)
145
+ # Resolve engine config live (Redis env → default) for this query so
146
+ # UI changes take effect immediately without restart or cache.
147
+ cfg = resolve_engine_config(self.organization, self._get_catalog())
148
+
149
+ chosen = engine if engine != Engine.AUTO else self._auto_pick(reflection, cfg)
142
150
 
143
151
  def timer_capture(evt: str):
144
152
  timer.capture_and_reset_timing(evt)
@@ -150,6 +158,7 @@ class Executor:
150
158
  query_manager=query_manager,
151
159
  timer_capture=timer_capture,
152
160
  log_prefix=log_prefix,
161
+ engine_config=cfg,
153
162
  )
154
163
  used = "duckdb_lite"
155
164
 
@@ -161,6 +170,7 @@ class Executor:
161
170
  query_manager=query_manager,
162
171
  timer_capture=timer_capture,
163
172
  log_prefix=log_prefix,
173
+ engine_config=cfg,
164
174
  )
165
175
  used = "duckdb_pro"
166
176
 
@@ -1525,6 +1525,20 @@ return 1
1525
1525
  return []
1526
1526
 
1527
1527
 
1528
+ def delete_spark_cluster(self, org: str, cluster_id: str) -> bool:
1529
+ """Remove a registered Spark Thrift cluster. Returns True if it existed."""
1530
+ if not (org and cluster_id):
1531
+ return False
1532
+ try:
1533
+ removed = self.r.hdel(RK.spark_thrifts(org), cluster_id)
1534
+ if removed:
1535
+ logger.info(f"[redis-catalog] spark thrift deleted: {org}/{cluster_id}")
1536
+ return bool(removed)
1537
+ except redis.RedisError as e:
1538
+ logger.error(f"[redis-catalog] delete_spark_cluster error: {e}")
1539
+ return False
1540
+
1541
+
1528
1542
  def select_spark_cluster(self, org: str, job_bytes: int, force: bool = False) -> Optional[Dict[str, Any]]:
1529
1543
  """
1530
1544
  Select the best active Spark Thrift cluster for a job of the given size.
@@ -1669,18 +1683,20 @@ return 1
1669
1683
  def set_engine_config(
1670
1684
  self,
1671
1685
  org: str,
1672
- sup: str,
1673
1686
  config: Dict[str, Any],
1674
1687
  ) -> bool:
1675
1688
  """Store engine runtime configuration (DuckDB + auto-pick thresholds).
1676
1689
 
1690
+ Org-level system scope: one engine config per organization, applied
1691
+ globally across all supertables (not per-supertable).
1692
+
1677
1693
  The config dict is stored as a JSON string. Only recognised fields
1678
1694
  (see ENGINE_CONFIG_FIELDS) are persisted — unknown keys are silently
1679
1695
  dropped to prevent injection of arbitrary settings.
1680
1696
 
1681
1697
  Existing config is fully replaced (last-write-wins).
1682
1698
  """
1683
- if not (org and sup):
1699
+ if not org:
1684
1700
  return False
1685
1701
  try:
1686
1702
  # Whitelist recognised fields only.
@@ -1691,7 +1707,7 @@ return 1
1691
1707
  }
1692
1708
  doc["modified_ms"] = _now_ms()
1693
1709
  self.r.set(
1694
- RK.config_engine(org, sup),
1710
+ RK.config_engine(org),
1695
1711
  json.dumps(doc, default=str),
1696
1712
  )
1697
1713
  return True
@@ -1702,16 +1718,15 @@ return 1
1702
1718
  def get_engine_config(
1703
1719
  self,
1704
1720
  org: str,
1705
- sup: str,
1706
1721
  ) -> Optional[Dict[str, Any]]:
1707
- """Retrieve engine runtime configuration.
1722
+ """Retrieve engine runtime configuration (org-level system scope).
1708
1723
 
1709
- Returns None if no config has been stored for this tenant.
1724
+ Returns None if no config has been stored for this organization.
1710
1725
  """
1711
- if not (org and sup):
1726
+ if not org:
1712
1727
  return None
1713
1728
  try:
1714
- raw = self.r.get(RK.config_engine(org, sup))
1729
+ raw = self.r.get(RK.config_engine(org))
1715
1730
  if raw:
1716
1731
  return json.loads(raw)
1717
1732
  except (redis.RedisError, json.JSONDecodeError) as e:
@@ -38,6 +38,7 @@ Hierarchy (v2)
38
38
  spark:
39
39
  thrifts HASH Spark Thrift clusters
40
40
  plugs HASH Spark Plug runtimes
41
+ config:engine STRING engine runtime config
41
42
  lakes: ← user-data sentinel
42
43
  {sup}: ← supertable scope
43
44
  meta:
@@ -55,7 +56,6 @@ Hierarchy (v2)
55
56
  pipes:
56
57
  index SET pipe names
57
58
  doc:{pipe_name} STRING pipe definition
58
- config:engine STRING engine runtime config
59
59
  lock:
60
60
  leaf:
61
61
  doc:{simple} STRING per-table lock token
@@ -572,12 +572,9 @@ def pipe_pattern(org: str, sup: str, staging_name: str) -> str:
572
572
 
573
573
  # --- Engine config --------------------------------------------------------- #
574
574
 
575
- def config_engine(org: str, sup: str) -> str:
576
- """Engine runtime config (STRING)."""
577
- return (
578
- f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{LAKES_SCOPE}"
579
- f":{_safe('sup', sup)}:config:engine"
580
- )
575
+ def config_engine(org: str) -> str:
576
+ """Engine runtime config (STRING). Org-level system scope (global, not per-supertable)."""
577
+ return f"{SUPERTABLE_PREFIX}:{_safe('org', org)}:{SYSTEM_SCOPE}:config:engine"
581
578
 
582
579
 
583
580
  # --- Locks ----------------------------------------------------------------- #
@@ -70,6 +70,7 @@ def _all_helpers() -> list[tuple[str, str, str]]:
70
70
  ("share_index", RK.share_index(ORG), f"{sys_pre}:shares:index"),
71
71
  ("spark_thrifts", RK.spark_thrifts(ORG), f"{sys_pre}:spark:thrifts"),
72
72
  ("spark_plugs", RK.spark_plugs(ORG), f"{sys_pre}:spark:plugs"),
73
+ ("config_engine", RK.config_engine(ORG), f"{sys_pre}:config:engine"),
73
74
 
74
75
  # ---- Lakes scope (per-org supertable enumeration) ---------------
75
76
  ("lakes_scope", RK.lakes_scope(ORG), f"supertable:{ORG}:lakes"),
@@ -95,9 +96,6 @@ def _all_helpers() -> list[tuple[str, str, str]]:
95
96
  ("pipe_doc", RK.pipe_doc(ORG, SUP, STAGING, PIPE), f"{lake_pre}:meta:staging:doc:{STAGING}:pipes:doc:{PIPE}"),
96
97
  ("pipe_pattern", RK.pipe_pattern(ORG, SUP, STAGING), f"{lake_pre}:meta:staging:doc:{STAGING}:pipes:doc:*"),
97
98
 
98
- # ---- Config -----------------------------------------------------
99
- ("config_engine", RK.config_engine(ORG, SUP), f"{lake_pre}:config:engine"),
100
-
101
99
  # ---- Locks ------------------------------------------------------
102
100
  ("lock_leaf", RK.lock_leaf(ORG, SUP, SIMPLE), f"{lake_pre}:lock:leaf:doc:{SIMPLE}"),
103
101
  ("lock_leaf_pattern", RK.lock_leaf_pattern(ORG, SUP), f"{lake_pre}:lock:leaf:doc:*"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supertable
3
- Version: 2.2.4
3
+ Version: 2.2.5
4
4
  Summary: SuperTable — versioned data lake library for SQL analytics on Parquet + Redis.
5
5
  Author: Levente Kupas
6
6
  Author-email: Levente Kupas <lkupas@kladnasoft.com>
@@ -99,6 +99,7 @@ supertable/engine/data_estimator.py
99
99
  supertable/engine/duckdb_lite.py
100
100
  supertable/engine/duckdb_pro.py
101
101
  supertable/engine/engine_common.py
102
+ supertable/engine/engine_config.py
102
103
  supertable/engine/engine_enum.py
103
104
  supertable/engine/executor.py
104
105
  supertable/engine/plan_stats.py
File without changes
File without changes
File without changes
File without changes