pytrilogy 0.3.330__tar.gz → 0.3.332__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.
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/PKG-INFO +1 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/__init__.py +1 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/authoring/__init__.py +2 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/execute.py +17 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/union_dim_pushdown.py +35 -5
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +87 -29
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/utility.py +47 -13
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/strategy_builder.py +36 -2
- pytrilogy-0.3.332/trilogy/core/processing/v4_node_generators/basic.py +104 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/query_processor.py +101 -18
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/statements/author.py +24 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/statements/execute.py +10 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/base.py +18 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/python_source.py +7 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/AGENTS.md +6 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/__init__.py +2 -0
- pytrilogy-0.3.332/trilogy/execution/state/isolation.py +39 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/partitions.py +19 -19
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/state_store.py +10 -14
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/watermarks.py +72 -30
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/executor.py +104 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parser.py +2 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/parse_engine_v2.py +4 -3
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/render.py +79 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/trilogy.lark +6 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/hydration.py +24 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/chart_rules.py +9 -2
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/operational_rules.py +53 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/statement_planner.py +3 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/statement_plans.py +32 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/syntax.py +2 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/rendering/altair_renderer.py +17 -9
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/content.py +19 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/cloud.py +634 -33
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/cloud_models.py +37 -10
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/common.py +18 -6
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/Cargo.lock +1 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/Cargo.toml +1 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/trilogy.pest +3 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display.py +2 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_parallel.py +67 -39
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/parallel_execution.py +11 -3
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/refresh.py +1 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/single_execution.py +1 -1
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/source.py +2 -5
- pytrilogy-0.3.330/trilogy/core/processing/v4_node_generators/basic.py +0 -53
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/.scripts/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/.scripts/build_backend.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/.scripts/sync_version.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/LICENSE.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/pyproject.toml +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/constants.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/conversation.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/enums.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/execute.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/models.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/prompts.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/anthropic.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/base.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/deepseek.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/google.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/openai.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/openrouter.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/providers/utils.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/ai/syntax_examples.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/authoring/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/constants.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/domain_graph.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/domain_validation.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/fingerprint.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/graph.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/having_normalization.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/author.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/build.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/models/environment.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/collapse_single_parent.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/filtered_aggregate.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/filtered_count_join.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/full_join_lowering.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/hide_unused_concept.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/join_hoist.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/join_upgrade.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/keyless_full_join.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/merge_irrelevant_group_by.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/null_safe_join.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/order_inner_joins.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/semi_join_pushdown.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/strip_redundant_not_null.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/utils.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/optimizations/value_set_join_upgrade.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/VIRTUAL_UNNEST.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/aggregate_rollup.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/concept_strategies_v4.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/condition_utility.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/constants.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/discovery_utility.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/discovery_validation.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/grain_utility.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/join_resolution.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/model_ambiguity.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/presence_probe.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/condition_routing.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/datasource_nodes.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_helpers/source_scoring.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/recursive_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/subselect_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/rowset_islanding.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/concept_graph.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/condition_injection.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/condition_placement.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/constants.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/edges.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/functional_dependency.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/group_behaviors.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/group_graph.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/group_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/history.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/models.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_build.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_coalescing.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_model.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_obligations.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_search.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/network_topology.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/projection.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/source_planning.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/staged_where.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/aggregate.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/common.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/condition_sources.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/constant.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/dispatch.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/filter.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/group_to.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/multiselect.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/nested_select.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/recursive.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/root.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/rowset.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/subselect.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/union.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/union_select.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/unnest.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_node_generators/window.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/scope_diagnostics.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/statements/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/table_processor.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/utility.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/common.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/concept.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/datasource.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/environment.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/fix.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/validation/rows.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/where_scope_normalization.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/bigquery_engine.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/bigquery_persist.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/bigquery_staging.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/cancel.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/clickhouse.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/clickhouse_chdb.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/duckdb_uv.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/metadata.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/mock.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/mysql.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/results.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/source_pushdown.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/dialect/sqlite.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/engine.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/config.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/envs.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/model_fingerprint.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/report.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/CLAUDE.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/cache.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/exceptions.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/persistence.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/phases.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/execution/state/snapshot.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/adapters.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/arrow.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/click_support.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/contract.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/describe.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/errors.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/runner.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/io/sinks.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/pretty.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/concept_factory.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/concept_syntax.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/errors.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/function_syntax.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/import_service.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/lark_backend.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/model.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/pest_backend.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rowset_semantics.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/concept_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/conditional_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/datasource_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/expression_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/function_definition_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/function_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/import_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/merge_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/multiselect_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/order_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/persist_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/rawsql_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/rowset_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/select_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/select_statement_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/statement_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/subselect_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/token_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/tvf_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules/type_rules.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/rules_context.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/select_finalize.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/semantic_scope.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/semantic_state.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/parsing/v2/symbols.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/py.typed +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/render.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/rendering/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/rendering/base.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/rendering/chart_theme.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/rendering/rich_types.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/rendering/terminal_renderer.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/rendering/theme.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/backends/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/backends/base.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/backends/html.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/backends/png.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/charts.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/document.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/runner.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/report/tables.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/AGENTS.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/CLAUDE.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_info.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/cli.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_info_docs/directory.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_sessions.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_sql_tools.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/agent_tools.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/click_utils.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/database.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/README.md +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/build.sh +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/examples/basic_usage.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/directory_resolver.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/graph.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/lib.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/main.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/network_search.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/parser.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/python_bindings.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/resolver.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/src/trilogy_parser.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/base.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/cli_integration.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/customer.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/main.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/orders.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/public_api.rs +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/test_data/base.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency/tests/test_data/consumer.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/dependency.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_core.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_execution.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_ingest.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_init.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_models.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_refresh.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/display_validation.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/env_commands.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/environment.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/explore.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/explore_seen.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/file.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/backends.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/preql_description.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/file_helpers/preql_validation.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/fmt.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/ingest.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/fk_inference.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/foreign_keys.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/formatting.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/introspection.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/ingest_helpers/typing.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/init.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/plan.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/project_config.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/public.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/render.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/run.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/connection_spec.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/file_discovery.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/index_generation.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/jobs.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/models.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/state_cache.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/state_computation.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/serve_helpers/studio_bundle.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/source_identity.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/state.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/testing.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/scripts/validate_agent.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/staging.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/chart.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/color.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/currency.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/metric.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/net.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/ranking.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/std/semantic.preql +0 -0
- {pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/utility.py +0 -0
|
@@ -55,6 +55,7 @@ from trilogy.core.models.datasource import Address, Datasource, DatasourceMetada
|
|
|
55
55
|
from trilogy.core.models.environment import DictImportResolver, Environment
|
|
56
56
|
from trilogy.core.statements.author import (
|
|
57
57
|
STATEMENT_TYPES,
|
|
58
|
+
CallStatement,
|
|
58
59
|
ConceptDeclarationStatement,
|
|
59
60
|
ConceptTransform,
|
|
60
61
|
CopyStatement,
|
|
@@ -89,6 +90,7 @@ __all__ = [
|
|
|
89
90
|
"ArrayType",
|
|
90
91
|
# trilogy.core.enums
|
|
91
92
|
"BooleanOperator",
|
|
93
|
+
"CallStatement",
|
|
92
94
|
"CaseElse",
|
|
93
95
|
"CaseWhen",
|
|
94
96
|
"Comment",
|
|
@@ -1830,11 +1830,27 @@ class UnionCTE:
|
|
|
1830
1830
|
|
|
1831
1831
|
@property
|
|
1832
1832
|
def group_to_grain(self) -> bool:
|
|
1833
|
+
"""A union never dedupes itself — only its consumer can decide.
|
|
1834
|
+
|
|
1835
|
+
A UNION ALL whose projection drops its arms' grain keys does hold
|
|
1836
|
+
duplicate rows against its own declared grain, so this is NOT the
|
|
1837
|
+
"already a unique set" it looks like. But the duplicates are only noise
|
|
1838
|
+
to a consumer joining the union as a key set; to a consumer aggregating
|
|
1839
|
+
it they are the data. `select species, sum(dbh)` over two tree-grain
|
|
1840
|
+
partitions projects (species, dbh) and must keep all three of three
|
|
1841
|
+
identically-valued trees — deduping here silently under-counts the sum.
|
|
1842
|
+
|
|
1843
|
+
The union cannot tell those apart, so the obligation sits with whoever
|
|
1844
|
+
reads it at a coarser grain: that consumer must group. See
|
|
1845
|
+
`_grain_claim_needs_group` in v4_node_generators/basic.py, which is the
|
|
1846
|
+
projection-shaped case of exactly that.
|
|
1847
|
+
"""
|
|
1833
1848
|
return False
|
|
1834
1849
|
|
|
1835
1850
|
@property
|
|
1836
1851
|
def group_concepts(self) -> list[BuildConcept]:
|
|
1837
|
-
#
|
|
1852
|
+
# Nothing to group by: see group_to_grain — deduping is the consumer's
|
|
1853
|
+
# obligation, never the union's.
|
|
1838
1854
|
return []
|
|
1839
1855
|
|
|
1840
1856
|
def __add__(self, other):
|
|
@@ -192,6 +192,23 @@ def _datasource_matches_raw_id(
|
|
|
192
192
|
return isinstance(base, BuildDatasource) and base.identifier == source_id
|
|
193
193
|
|
|
194
194
|
|
|
195
|
+
def _derives_from(node: CTE | UnionCTE, container_name: str) -> bool:
|
|
196
|
+
"""``node`` IS ``container_name`` or reads from it, at any depth."""
|
|
197
|
+
seen: set[str] = set()
|
|
198
|
+
stack: list[CTE | UnionCTE] = [node]
|
|
199
|
+
while stack:
|
|
200
|
+
current = stack.pop()
|
|
201
|
+
if current.name == container_name:
|
|
202
|
+
return True
|
|
203
|
+
if current.name in seen:
|
|
204
|
+
continue
|
|
205
|
+
seen.add(current.name)
|
|
206
|
+
stack.extend(current.dependency_nodes())
|
|
207
|
+
if isinstance(current, UnionCTE):
|
|
208
|
+
stack.extend(current.internal_ctes)
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
|
|
195
212
|
def _dim_local_atoms(
|
|
196
213
|
cte: CTE,
|
|
197
214
|
dim_qds: BuildDatasource | QueryDatasource,
|
|
@@ -604,12 +621,25 @@ class UnionDimPushdown(OptimizationRule):
|
|
|
604
621
|
return needed.issubset(out)
|
|
605
622
|
|
|
606
623
|
def _resolve_push_context(
|
|
607
|
-
self, consumers: list[CTE], d: _DimDescriptor
|
|
624
|
+
self, consumers: list[CTE], d: _DimDescriptor, container: CTE | UnionCTE
|
|
608
625
|
) -> _PushContext | None:
|
|
609
626
|
for c in consumers:
|
|
610
627
|
found = _find_dim_cte_for_qds(c, d.join_qds_id)
|
|
611
|
-
if found is
|
|
612
|
-
|
|
628
|
+
if found is None or not self._dim_cte_exposes(found, d):
|
|
629
|
+
continue
|
|
630
|
+
# A "dim" carved OUT of the container (a single-key projection of the
|
|
631
|
+
# union, which the planner peels when the union is the only source of
|
|
632
|
+
# that key) cannot be computed before it. Pushing it in would make the
|
|
633
|
+
# container's branches depend on a CTE that depends on the container —
|
|
634
|
+
# `reorder_ctes` then fails the whole query with "CTE dependency graph
|
|
635
|
+
# contains a cycle".
|
|
636
|
+
if _derives_from(found, container.name):
|
|
637
|
+
self.log(
|
|
638
|
+
f"Skipping dim {d.dim_qds.identifier}: its CTE {found.name} "
|
|
639
|
+
f"is derived from {container.name}"
|
|
640
|
+
)
|
|
641
|
+
continue
|
|
642
|
+
return _PushContext(dim_cte=found, source_consumer=c)
|
|
613
643
|
return None
|
|
614
644
|
|
|
615
645
|
def _can_push_into_branch(self, branch: CTE, d: _DimDescriptor) -> bool:
|
|
@@ -634,7 +664,7 @@ class UnionDimPushdown(OptimizationRule):
|
|
|
634
664
|
if not d.strip_safe and not d.where_atoms:
|
|
635
665
|
return False
|
|
636
666
|
|
|
637
|
-
context = self._resolve_push_context(consumers, d)
|
|
667
|
+
context = self._resolve_push_context(consumers, d, union)
|
|
638
668
|
if context is None:
|
|
639
669
|
return False
|
|
640
670
|
|
|
@@ -707,7 +737,7 @@ class UnionDimPushdown(OptimizationRule):
|
|
|
707
737
|
"""
|
|
708
738
|
if not d.strip_safe or not d.where_atoms:
|
|
709
739
|
return False
|
|
710
|
-
context = self._resolve_push_context(consumers, d)
|
|
740
|
+
context = self._resolve_push_context(consumers, d, target)
|
|
711
741
|
if context is None:
|
|
712
742
|
return False
|
|
713
743
|
if not self._can_push_into_branch(target, d):
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
|
-
from itertools import product
|
|
3
2
|
|
|
4
3
|
from trilogy.core.enums import (
|
|
5
4
|
AddressType,
|
|
@@ -76,9 +75,12 @@ def _best_enum_union(
|
|
|
76
75
|
) -> list[list[BuildDatasource]] | None:
|
|
77
76
|
"""Find the best minimal covering combinations for an enum-partitioned key.
|
|
78
77
|
|
|
79
|
-
Groups by covered enum value,
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
Groups by covered enum value, then searches one-source-per-value combos via
|
|
79
|
+
a dynamic program over values whose state is the combo's concept overlap so
|
|
80
|
+
far (minus the merge key), keeping the highest-scoring combo per distinct
|
|
81
|
+
overlap signature. The score is separable (a per-source sum) and the
|
|
82
|
+
overlap is the only coupling between values, so this is exhaustive over
|
|
83
|
+
achievable signatures without enumerating the k^V combo product. Returning
|
|
82
84
|
one combo per overlap lets parallel partitionings (e.g., sales vs.
|
|
83
85
|
returns vs. dim, all keyed by the same channel enum) each contribute
|
|
84
86
|
their own union datasource instead of collapsing into the single best.
|
|
@@ -100,36 +102,92 @@ def _best_enum_union(
|
|
|
100
102
|
return None
|
|
101
103
|
|
|
102
104
|
values = list(by_value.keys())
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
# A union requires at least 2 distinct sources; a single source is not a union
|
|
106
|
+
if len(values) < 2:
|
|
107
|
+
return None
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
cols: dict[int, frozenset[str]] = {}
|
|
110
|
+
scores: dict[int, int] = {}
|
|
111
|
+
for candidates in by_value.values():
|
|
112
|
+
for ds in candidates:
|
|
113
|
+
cols[id(ds)] = frozenset(col.concept.address for col in ds.columns)
|
|
114
|
+
scores[id(ds)] = _datasource_score(ds)
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
# Members MAY disagree on intrinsic (~) partiality of a shared column
|
|
117
|
+
# (a "mixed-family" combo, e.g. web_sales + catalog/store_returns).
|
|
118
|
+
# Such a union is a legitimate per-channel provider of columns it binds
|
|
119
|
+
# complete (q05: a web return's return-site lives on web_sales), and
|
|
120
|
+
# union partial propagation keeps its ~-partial keys from ever outranking
|
|
121
|
+
# a pure family that binds them complete (q14) — so don't reject it here.
|
|
122
|
+
# An empty overlap beyond the merge key IS rejected, at the first step it
|
|
123
|
+
# appears: intersection only shrinks, so no continuation can revive it.
|
|
124
|
+
# Ties reproduce the old product enumeration exactly: per signature the
|
|
125
|
+
# winner is the first max-scoring combo in product order (`combo_key` =
|
|
126
|
+
# candidate index tuple, lexicographic = product order), and signatures
|
|
127
|
+
# order by their first-achieving combo (`min_key`, tracked over ALL combos
|
|
128
|
+
# reaching a signature, not just the best-scoring one).
|
|
129
|
+
merge_key_addr = merge_key.address
|
|
130
|
+
# signature -> (score, combo, combo_key, min_key)
|
|
131
|
+
states: dict[
|
|
132
|
+
frozenset[str],
|
|
133
|
+
tuple[int, list[BuildDatasource], tuple[int, ...], tuple[int, ...]],
|
|
134
|
+
] = {}
|
|
135
|
+
for idx, ds in enumerate(by_value[values[0]]):
|
|
136
|
+
sig = cols[id(ds)] - {merge_key_addr}
|
|
137
|
+
if not sig:
|
|
112
138
|
continue
|
|
139
|
+
existing = states.get(sig)
|
|
140
|
+
if existing is None:
|
|
141
|
+
states[sig] = (scores[id(ds)], [ds], (idx,), (idx,))
|
|
142
|
+
elif scores[id(ds)] > existing[0]:
|
|
143
|
+
states[sig] = (scores[id(ds)], [ds], (idx,), existing[3])
|
|
113
144
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
for v in values[1:]:
|
|
146
|
+
next_states: dict[
|
|
147
|
+
frozenset[str],
|
|
148
|
+
tuple[int, list[BuildDatasource], tuple[int, ...], tuple[int, ...]],
|
|
149
|
+
] = {}
|
|
150
|
+
for sig, (score, combo, combo_key, min_key) in states.items():
|
|
151
|
+
for idx, ds in enumerate(by_value[v]):
|
|
152
|
+
new_sig = sig & cols[id(ds)]
|
|
153
|
+
if not new_sig:
|
|
154
|
+
continue
|
|
155
|
+
new_score = score + scores[id(ds)]
|
|
156
|
+
new_key = combo_key + (idx,)
|
|
157
|
+
existing = next_states.get(new_sig)
|
|
158
|
+
if existing is None:
|
|
159
|
+
next_states[new_sig] = (
|
|
160
|
+
new_score,
|
|
161
|
+
combo + [ds],
|
|
162
|
+
new_key,
|
|
163
|
+
min_key + (idx,),
|
|
164
|
+
)
|
|
165
|
+
continue
|
|
166
|
+
new_min_key = min(existing[3], min_key + (idx,))
|
|
167
|
+
if new_score > existing[0] or (
|
|
168
|
+
new_score == existing[0] and new_key < existing[2]
|
|
169
|
+
):
|
|
170
|
+
next_states[new_sig] = (
|
|
171
|
+
new_score,
|
|
172
|
+
combo + [ds],
|
|
173
|
+
new_key,
|
|
174
|
+
new_min_key,
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
next_states[new_sig] = (
|
|
178
|
+
existing[0],
|
|
179
|
+
existing[1],
|
|
180
|
+
existing[2],
|
|
181
|
+
new_min_key,
|
|
182
|
+
)
|
|
183
|
+
states = next_states
|
|
184
|
+
if not states:
|
|
185
|
+
return None
|
|
132
186
|
|
|
187
|
+
best_per_overlap: dict[frozenset[str], tuple[list[BuildDatasource], int]] = {
|
|
188
|
+
sig: (state[1], state[0])
|
|
189
|
+
for sig, state in sorted(states.items(), key=lambda kv: kv[1][3])
|
|
190
|
+
}
|
|
133
191
|
if not best_per_overlap:
|
|
134
192
|
return None
|
|
135
193
|
# Keep only maximal overlap signatures: drop a signature whose concept set
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Sequence
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
@@ -8,6 +9,7 @@ if TYPE_CHECKING:
|
|
|
8
9
|
from trilogy.core import graph as nx
|
|
9
10
|
|
|
10
11
|
from trilogy.core.enums import Derivation, Granularity, JoinType, Purpose
|
|
12
|
+
from trilogy.core.models.author import ConceptRef
|
|
11
13
|
from trilogy.core.models.build import (
|
|
12
14
|
BuildConcept,
|
|
13
15
|
BuildDatasource,
|
|
@@ -210,6 +212,44 @@ def find_nullable_concepts(
|
|
|
210
212
|
return sorted(final_nullable)
|
|
211
213
|
|
|
212
214
|
|
|
215
|
+
def _resolve_output_target(
|
|
216
|
+
cte: CTE | UnionCTE,
|
|
217
|
+
target: BuildConcept | ConceptRef,
|
|
218
|
+
scoped_merge_map: dict[str, str],
|
|
219
|
+
) -> tuple[BuildConcept, bool] | None:
|
|
220
|
+
"""The CTE column that renders `target`, and whether it must be re-aliased
|
|
221
|
+
to the written name. None when the CTE cannot render it at all."""
|
|
222
|
+
mapping = {x.address: x for x in cte.output_columns}
|
|
223
|
+
if target.address in mapping:
|
|
224
|
+
return mapping[target.address], False
|
|
225
|
+
for oc in cte.output_columns:
|
|
226
|
+
if target.address in oc.pseudonyms:
|
|
227
|
+
return oc, True
|
|
228
|
+
# an in-query JOIN collapses a source onto its canonical target; the
|
|
229
|
+
# target's column is present, render it under the written source name
|
|
230
|
+
canonical = scoped_merge_map.get(target.address)
|
|
231
|
+
if canonical is not None and canonical in mapping:
|
|
232
|
+
return mapping[canonical], True
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def unrenderable_outputs(
|
|
237
|
+
cte: CTE | UnionCTE,
|
|
238
|
+
targets: Sequence[BuildConcept | ConceptRef],
|
|
239
|
+
scoped_merge_map: dict[str, str] | None = None,
|
|
240
|
+
) -> list[str]:
|
|
241
|
+
"""Target addresses `sort_select_output` would silently omit from the
|
|
242
|
+
projection. A caller that cannot tolerate a short SELECT — a persist writes
|
|
243
|
+
positionally, so a missing column shifts every later one — asks here and
|
|
244
|
+
fails naming the column instead."""
|
|
245
|
+
merge_map = scoped_merge_map or {}
|
|
246
|
+
return [
|
|
247
|
+
target.address
|
|
248
|
+
for target in targets
|
|
249
|
+
if _resolve_output_target(cte, target, merge_map) is None
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
|
|
213
253
|
def sort_select_output_processed(
|
|
214
254
|
cte: CTE | UnionCTE, query: SelectStatement | MultiSelectStatement | ProcessedQuery
|
|
215
255
|
) -> CTE | UnionCTE:
|
|
@@ -221,7 +261,6 @@ def sort_select_output_processed(
|
|
|
221
261
|
hidden = query.hidden_components
|
|
222
262
|
|
|
223
263
|
output_addresses = {c.address for c in targets}
|
|
224
|
-
mapping = {x.address: x for x in cte.output_columns}
|
|
225
264
|
scoped_merge_map: dict[str, str] = (
|
|
226
265
|
query.scoped_merge_map if isinstance(query, ProcessedQuery) else {}
|
|
227
266
|
)
|
|
@@ -243,19 +282,14 @@ def sort_select_output_processed(
|
|
|
243
282
|
|
|
244
283
|
new_output: list[BuildConcept] = []
|
|
245
284
|
for x in targets:
|
|
246
|
-
|
|
247
|
-
|
|
285
|
+
# A target the CTE cannot render is dropped here; `unrenderable_outputs`
|
|
286
|
+
# is the same resolution, asked ahead of time by callers that must fail
|
|
287
|
+
# rather than emit a short projection.
|
|
288
|
+
resolved = _resolve_output_target(cte, x, scoped_merge_map)
|
|
289
|
+
if resolved is None:
|
|
248
290
|
continue
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
new_output.append(render_as(x, oc))
|
|
252
|
-
break
|
|
253
|
-
else:
|
|
254
|
-
# an in-query JOIN collapses a source onto its canonical target; the
|
|
255
|
-
# target's column is present, render it under the written source name
|
|
256
|
-
canonical = scoped_merge_map.get(x.address)
|
|
257
|
-
if canonical is not None and canonical in mapping:
|
|
258
|
-
new_output.append(render_as(x, mapping[canonical]))
|
|
291
|
+
source, rename = resolved
|
|
292
|
+
new_output.append(render_as(x, source) if rename else source)
|
|
259
293
|
|
|
260
294
|
for oc in cte.output_columns:
|
|
261
295
|
# add hidden back
|
{pytrilogy-0.3.330 → pytrilogy-0.3.332}/trilogy/core/processing/v4_helper/strategy_builder.py
RENAMED
|
@@ -2019,7 +2019,19 @@ def _satisfy_parent_projection_contract(
|
|
|
2019
2019
|
if not any(_contains_shape_barrier(parent) for parent in parents):
|
|
2020
2020
|
return parents
|
|
2021
2021
|
|
|
2022
|
+
# Two different questions, two different sets. "What is available TO this
|
|
2023
|
+
# parent" is its own parents' outputs (`parent_output_addresses`) — that is
|
|
2024
|
+
# what `parent_needed` asks, and a leaf scan's empty answer is what keeps
|
|
2025
|
+
# leaf scans out of this projection entirely (q66). "What does a SIBLING
|
|
2026
|
+
# supply to the merge" is that sibling's own projection: a WINDOW sibling
|
|
2027
|
+
# READS `event_time` and emits only `lag(event_time)`, so crediting it with
|
|
2028
|
+
# its inputs made this projection strip `event_time` off the dimension
|
|
2029
|
+
# parent — and the consumer computing `event_time - prior_event_time` then
|
|
2030
|
+
# had no source for it and was dropped from the plan outright.
|
|
2022
2031
|
outputs_by_parent = [parent_output_addresses(parent) for parent in parents]
|
|
2032
|
+
own_outputs_by_parent = [
|
|
2033
|
+
{output.address for output in parent.usable_outputs} for parent in parents
|
|
2034
|
+
]
|
|
2023
2035
|
projected: list[StrategyNode] = []
|
|
2024
2036
|
for idx, parent in enumerate(parents):
|
|
2025
2037
|
if _contains_shape_barrier(parent):
|
|
@@ -2027,7 +2039,7 @@ def _satisfy_parent_projection_contract(
|
|
|
2027
2039
|
continue
|
|
2028
2040
|
parent_needed = outputs_by_parent[idx] & needed
|
|
2029
2041
|
other_outputs = set().union(
|
|
2030
|
-
*(outputs for j, outputs in enumerate(
|
|
2042
|
+
*(outputs for j, outputs in enumerate(own_outputs_by_parent) if j != idx)
|
|
2031
2043
|
)
|
|
2032
2044
|
fd_candidates = {
|
|
2033
2045
|
addr
|
|
@@ -2049,6 +2061,28 @@ def _satisfy_parent_projection_contract(
|
|
|
2049
2061
|
if not concepts or non_fd_needed:
|
|
2050
2062
|
projected.append(parent)
|
|
2051
2063
|
continue
|
|
2064
|
+
# `parent_needed` is measured off the parent's INPUTS, so a value the
|
|
2065
|
+
# parent computes itself — a BASIC sibling's `revenue`, a date
|
|
2066
|
+
# projection — is invisible to it and the projection below would strip
|
|
2067
|
+
# it, leaving the consuming aggregate with no source for that output
|
|
2068
|
+
# (sibling aggregates where one reads a derived row value: the plan
|
|
2069
|
+
# computed `revenue` in a CTE and then never projected `total_revenue`).
|
|
2070
|
+
# Carry those through. Restricted to FD-at-grain so the projection's
|
|
2071
|
+
# row count is unchanged, and to what no sibling parent already
|
|
2072
|
+
# supplies, so this never re-shapes a plain dimension re-join.
|
|
2073
|
+
carry = {
|
|
2074
|
+
output.address
|
|
2075
|
+
for output in parent.usable_outputs
|
|
2076
|
+
if output.address in needed
|
|
2077
|
+
and output.address not in parent_needed
|
|
2078
|
+
and output.address not in other_outputs
|
|
2079
|
+
and _fd_at_grain(output, projection_grain_components)
|
|
2080
|
+
}
|
|
2081
|
+
concepts.extend(
|
|
2082
|
+
c
|
|
2083
|
+
for addr in sorted(carry)
|
|
2084
|
+
if (c := _concept_at(environment, addr)) is not None
|
|
2085
|
+
)
|
|
2052
2086
|
# When the dimension's projected grain (fd_needed) shares NO key with the
|
|
2053
2087
|
# barrier sibling, the post-projection merge has nothing to join on and
|
|
2054
2088
|
# cross-joins ON 1=1 — the bridge between the two is a projection-grain key
|
|
@@ -2079,7 +2113,7 @@ def _satisfy_parent_projection_contract(
|
|
|
2079
2113
|
parent_outputs = {o.address for o in parent.usable_outputs}
|
|
2080
2114
|
grain_concepts = [
|
|
2081
2115
|
c
|
|
2082
|
-
for addr in sorted((fd_needed | join_keys) & parent_outputs)
|
|
2116
|
+
for addr in sorted((fd_needed | join_keys | carry) & parent_outputs)
|
|
2083
2117
|
if (c := _concept_at(environment, addr)) is not None
|
|
2084
2118
|
]
|
|
2085
2119
|
projected.append(
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from trilogy.core.models.build import BuildConcept, BuildGrain, BuildWhereClause
|
|
2
|
+
from trilogy.core.models.build_environment import BuildEnvironment
|
|
3
|
+
from trilogy.core.processing.nodes import (
|
|
4
|
+
ConstantNode,
|
|
5
|
+
MergeNode,
|
|
6
|
+
SelectNode,
|
|
7
|
+
StrategyNode,
|
|
8
|
+
)
|
|
9
|
+
from trilogy.core.processing.v4_helper.functional_dependency import (
|
|
10
|
+
build_fd_determines,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .common import outputs_with_parent_grain_keys, parent_outputs_needed
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _grain_claim_needs_group(
|
|
17
|
+
outputs: list[BuildConcept],
|
|
18
|
+
parent: StrategyNode,
|
|
19
|
+
environment: BuildEnvironment,
|
|
20
|
+
) -> bool:
|
|
21
|
+
"""True when this projection can only make its grain claim true by grouping.
|
|
22
|
+
|
|
23
|
+
A projection's grain is inferred from what it projects, but a plain SELECT
|
|
24
|
+
emits one row per PARENT row. Project `species` and `upper(species)` off a
|
|
25
|
+
`tree_id`-grain union and the node advertises grain `species` while emitting
|
|
26
|
+
one row per tree; a consumer then joins it on `species` and fans the other
|
|
27
|
+
side out — silently, since the SQL is valid.
|
|
28
|
+
|
|
29
|
+
Two things can make the claim true, and only the second is ours to do here:
|
|
30
|
+
|
|
31
|
+
- The claim is already true. Either the parent's grain keys survive in the
|
|
32
|
+
projection, or the projected grain functionally determines them (the
|
|
33
|
+
parent holds at most one row per it, so dropping them loses nothing).
|
|
34
|
+
- The projection can be widened back to the parent's grain. That is
|
|
35
|
+
``outputs_with_parent_grain_keys``'s job, and it needs the parent to
|
|
36
|
+
actually expose those keys.
|
|
37
|
+
|
|
38
|
+
When the parent does NOT expose its own grain there is nothing to widen with,
|
|
39
|
+
and grouping is the only repair left. The outputs are functionally determined
|
|
40
|
+
by the grouped set, so the dedupe cannot drop a row — it removes exactly the
|
|
41
|
+
duplicates the parent's finer grain introduced.
|
|
42
|
+
|
|
43
|
+
Returns SelectNode's own default (False) when it does not fire -- passing
|
|
44
|
+
None instead would flip every other basic projection onto ``_resolve``'s
|
|
45
|
+
grain-less branch, which is a plan change for nodes this has no business
|
|
46
|
+
touching.
|
|
47
|
+
"""
|
|
48
|
+
parent_grain = set(parent.resolve().grain.components)
|
|
49
|
+
if not parent_grain or parent_grain.issubset({c.address for c in outputs}):
|
|
50
|
+
return False
|
|
51
|
+
if parent_grain.issubset({c.address for c in parent.output_concepts}):
|
|
52
|
+
return False
|
|
53
|
+
claimed = BuildGrain.from_concepts(outputs).components
|
|
54
|
+
return not all(
|
|
55
|
+
component in claimed
|
|
56
|
+
or build_fd_determines(
|
|
57
|
+
environment, claimed, component, include_empty_grain=False
|
|
58
|
+
)
|
|
59
|
+
for component in parent_grain
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def gen_basic(
|
|
64
|
+
outputs: list[BuildConcept],
|
|
65
|
+
parents: list[StrategyNode],
|
|
66
|
+
environment: BuildEnvironment,
|
|
67
|
+
conditions: BuildWhereClause | None = None,
|
|
68
|
+
preexisting_conditions: BuildWhereClause | None = None,
|
|
69
|
+
) -> StrategyNode | None:
|
|
70
|
+
"""Projection of derived basic expressions over already-built parents.
|
|
71
|
+
|
|
72
|
+
Zero parents → ConstantNode. One parent → SelectNode (just projection).
|
|
73
|
+
Multiple parents → MergeNode, which auto-joins on shared output concepts
|
|
74
|
+
(typically the common grain). A SelectNode here would render with no
|
|
75
|
+
join and emit `INVALID_REFERENCE_BUG_<...>` for the unjoined parent."""
|
|
76
|
+
pre = preexisting_conditions.conditional if preexisting_conditions else None
|
|
77
|
+
if not parents:
|
|
78
|
+
return ConstantNode(
|
|
79
|
+
input_concepts=[],
|
|
80
|
+
output_concepts=outputs,
|
|
81
|
+
environment=environment,
|
|
82
|
+
conditions=conditions.conditional if conditions else None,
|
|
83
|
+
preexisting_conditions=pre,
|
|
84
|
+
)
|
|
85
|
+
full_outputs = outputs_with_parent_grain_keys(outputs, parents)
|
|
86
|
+
inputs = parent_outputs_needed(full_outputs, parents, conditions)
|
|
87
|
+
if len(parents) == 1:
|
|
88
|
+
return SelectNode(
|
|
89
|
+
input_concepts=inputs,
|
|
90
|
+
output_concepts=full_outputs,
|
|
91
|
+
environment=environment,
|
|
92
|
+
parents=parents,
|
|
93
|
+
conditions=conditions.conditional if conditions else None,
|
|
94
|
+
preexisting_conditions=pre,
|
|
95
|
+
force_group=_grain_claim_needs_group(full_outputs, parents[0], environment),
|
|
96
|
+
)
|
|
97
|
+
return MergeNode(
|
|
98
|
+
input_concepts=inputs,
|
|
99
|
+
output_concepts=full_outputs,
|
|
100
|
+
environment=environment,
|
|
101
|
+
parents=parents,
|
|
102
|
+
conditions=conditions.conditional if conditions else None,
|
|
103
|
+
preexisting_conditions=pre,
|
|
104
|
+
)
|