pytrilogy 0.0.3.99__tar.gz → 0.0.3.101__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.
Potentially problematic release.
This version of pytrilogy might be problematic. Click here for more details.
- {pytrilogy-0.0.3.99/pytrilogy.egg-info → pytrilogy-0.0.3.101}/PKG-INFO +15 -7
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/README.md +14 -6
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101/pytrilogy.egg-info}/PKG-INFO +15 -7
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/pytrilogy.egg-info/SOURCES.txt +1 -1
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_discovery_nodes.py +3 -3
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_parsing.py +1 -1
- pytrilogy-0.0.3.101/tests/test_validators.py +209 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/constants.py +1 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/exceptions.py +1 -1
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/functions.py +5 -2
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/core.py +3 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/execute.py +14 -7
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/concept_strategies_v3.py +7 -37
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/discovery_utility.py +30 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/basic_node.py +12 -18
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/group_node.py +4 -2
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/node_merge_node.py +16 -5
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/unnest_node.py +30 -3
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/group_node.py +6 -4
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/merge_node.py +9 -1
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/select_node_v2.py +1 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/validation/datasource.py +30 -6
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/base.py +1 -1
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/render.py +5 -3
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/trilogy.lark +6 -3
- pytrilogy-0.0.3.99/trilogy/core/processing/discovery_loop.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/setup.cfg +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/setup.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_execute_models.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_failure.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_models.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_parsing_failures.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_user_functions.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/author.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/build.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/models/environment.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/discovery_node_factory.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/discovery_validation.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/common.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/constant_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/recursive_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/statements/author.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/utility.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/validation/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/validation/common.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/validation/concept.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/validation/environment.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/core/validation/fix.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/metadata.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/common.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/parsing/parse_engine.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/metric.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/net.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/ranking.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.0.3.99 → pytrilogy-0.0.3.101}/trilogy/utility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.3.
|
|
3
|
+
Version: 0.0.3.101
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -123,15 +123,12 @@ Versus SQL, Trilogy aims to:
|
|
|
123
123
|
**Improve:**
|
|
124
124
|
- Simplicity
|
|
125
125
|
- Refactoring/maintainability
|
|
126
|
-
- Reusability
|
|
126
|
+
- Reusability/composability
|
|
127
|
+
- Expressivness
|
|
127
128
|
|
|
128
129
|
**Maintain:**
|
|
129
130
|
- Acceptable performance
|
|
130
131
|
|
|
131
|
-
**Remove:**
|
|
132
|
-
- Lower-level procedural features
|
|
133
|
-
- Transactional optimizations/non-analytics features
|
|
134
|
-
|
|
135
132
|
## Backend Support
|
|
136
133
|
|
|
137
134
|
| Backend | Status | Notes |
|
|
@@ -161,6 +158,7 @@ property sentence_id.word_three string::word; # a description to it
|
|
|
161
158
|
# comments in other places are just comments
|
|
162
159
|
|
|
163
160
|
# define our datasource to bind the model to data
|
|
161
|
+
# for most work, you can import something already defined
|
|
164
162
|
# testing using query fixtures is a common pattern
|
|
165
163
|
datasource word_one(
|
|
166
164
|
sentence: sentence_id,
|
|
@@ -323,7 +321,7 @@ from pytrilogy import Executor, Dialect
|
|
|
323
321
|
|
|
324
322
|
### Authoring Imports
|
|
325
323
|
|
|
326
|
-
Are also stable, and should be used for cases which programatically generate Trilogy statements without
|
|
324
|
+
Are also stable, and should be used for cases which programatically generate Trilogy statements without text inputs
|
|
327
325
|
or need to process/transform parsed code in more complicated ways.
|
|
328
326
|
|
|
329
327
|
```python
|
|
@@ -391,6 +389,16 @@ datasource <name>(
|
|
|
391
389
|
)
|
|
392
390
|
grain(<concept>, <concept>)
|
|
393
391
|
address <table>;
|
|
392
|
+
|
|
393
|
+
datasource orders(
|
|
394
|
+
order_id,
|
|
395
|
+
order_date,
|
|
396
|
+
total_rev: point_of_sale_rev,
|
|
397
|
+
customomer_id: customer.id
|
|
398
|
+
)
|
|
399
|
+
grain orders
|
|
400
|
+
address orders;
|
|
401
|
+
|
|
394
402
|
```
|
|
395
403
|
|
|
396
404
|
### Queries
|
|
@@ -85,15 +85,12 @@ Versus SQL, Trilogy aims to:
|
|
|
85
85
|
**Improve:**
|
|
86
86
|
- Simplicity
|
|
87
87
|
- Refactoring/maintainability
|
|
88
|
-
- Reusability
|
|
88
|
+
- Reusability/composability
|
|
89
|
+
- Expressivness
|
|
89
90
|
|
|
90
91
|
**Maintain:**
|
|
91
92
|
- Acceptable performance
|
|
92
93
|
|
|
93
|
-
**Remove:**
|
|
94
|
-
- Lower-level procedural features
|
|
95
|
-
- Transactional optimizations/non-analytics features
|
|
96
|
-
|
|
97
94
|
## Backend Support
|
|
98
95
|
|
|
99
96
|
| Backend | Status | Notes |
|
|
@@ -123,6 +120,7 @@ property sentence_id.word_three string::word; # a description to it
|
|
|
123
120
|
# comments in other places are just comments
|
|
124
121
|
|
|
125
122
|
# define our datasource to bind the model to data
|
|
123
|
+
# for most work, you can import something already defined
|
|
126
124
|
# testing using query fixtures is a common pattern
|
|
127
125
|
datasource word_one(
|
|
128
126
|
sentence: sentence_id,
|
|
@@ -285,7 +283,7 @@ from pytrilogy import Executor, Dialect
|
|
|
285
283
|
|
|
286
284
|
### Authoring Imports
|
|
287
285
|
|
|
288
|
-
Are also stable, and should be used for cases which programatically generate Trilogy statements without
|
|
286
|
+
Are also stable, and should be used for cases which programatically generate Trilogy statements without text inputs
|
|
289
287
|
or need to process/transform parsed code in more complicated ways.
|
|
290
288
|
|
|
291
289
|
```python
|
|
@@ -353,6 +351,16 @@ datasource <name>(
|
|
|
353
351
|
)
|
|
354
352
|
grain(<concept>, <concept>)
|
|
355
353
|
address <table>;
|
|
354
|
+
|
|
355
|
+
datasource orders(
|
|
356
|
+
order_id,
|
|
357
|
+
order_date,
|
|
358
|
+
total_rev: point_of_sale_rev,
|
|
359
|
+
customomer_id: customer.id
|
|
360
|
+
)
|
|
361
|
+
grain orders
|
|
362
|
+
address orders;
|
|
363
|
+
|
|
356
364
|
```
|
|
357
365
|
|
|
358
366
|
### Queries
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.3.
|
|
3
|
+
Version: 0.0.3.101
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -123,15 +123,12 @@ Versus SQL, Trilogy aims to:
|
|
|
123
123
|
**Improve:**
|
|
124
124
|
- Simplicity
|
|
125
125
|
- Refactoring/maintainability
|
|
126
|
-
- Reusability
|
|
126
|
+
- Reusability/composability
|
|
127
|
+
- Expressivness
|
|
127
128
|
|
|
128
129
|
**Maintain:**
|
|
129
130
|
- Acceptable performance
|
|
130
131
|
|
|
131
|
-
**Remove:**
|
|
132
|
-
- Lower-level procedural features
|
|
133
|
-
- Transactional optimizations/non-analytics features
|
|
134
|
-
|
|
135
132
|
## Backend Support
|
|
136
133
|
|
|
137
134
|
| Backend | Status | Notes |
|
|
@@ -161,6 +158,7 @@ property sentence_id.word_three string::word; # a description to it
|
|
|
161
158
|
# comments in other places are just comments
|
|
162
159
|
|
|
163
160
|
# define our datasource to bind the model to data
|
|
161
|
+
# for most work, you can import something already defined
|
|
164
162
|
# testing using query fixtures is a common pattern
|
|
165
163
|
datasource word_one(
|
|
166
164
|
sentence: sentence_id,
|
|
@@ -323,7 +321,7 @@ from pytrilogy import Executor, Dialect
|
|
|
323
321
|
|
|
324
322
|
### Authoring Imports
|
|
325
323
|
|
|
326
|
-
Are also stable, and should be used for cases which programatically generate Trilogy statements without
|
|
324
|
+
Are also stable, and should be used for cases which programatically generate Trilogy statements without text inputs
|
|
327
325
|
or need to process/transform parsed code in more complicated ways.
|
|
328
326
|
|
|
329
327
|
```python
|
|
@@ -391,6 +389,16 @@ datasource <name>(
|
|
|
391
389
|
)
|
|
392
390
|
grain(<concept>, <concept>)
|
|
393
391
|
address <table>;
|
|
392
|
+
|
|
393
|
+
datasource orders(
|
|
394
|
+
order_id,
|
|
395
|
+
order_date,
|
|
396
|
+
total_rev: point_of_sale_rev,
|
|
397
|
+
customomer_id: customer.id
|
|
398
|
+
)
|
|
399
|
+
grain orders
|
|
400
|
+
address orders;
|
|
401
|
+
|
|
394
402
|
```
|
|
395
403
|
|
|
396
404
|
### Queries
|
|
@@ -34,6 +34,7 @@ tests/test_statements.py
|
|
|
34
34
|
tests/test_typing.py
|
|
35
35
|
tests/test_undefined_concept.py
|
|
36
36
|
tests/test_user_functions.py
|
|
37
|
+
tests/test_validators.py
|
|
37
38
|
tests/test_where_clause.py
|
|
38
39
|
trilogy/__init__.py
|
|
39
40
|
trilogy/constants.py
|
|
@@ -71,7 +72,6 @@ trilogy/core/optimizations/inline_datasource.py
|
|
|
71
72
|
trilogy/core/optimizations/predicate_pushdown.py
|
|
72
73
|
trilogy/core/processing/__init__.py
|
|
73
74
|
trilogy/core/processing/concept_strategies_v3.py
|
|
74
|
-
trilogy/core/processing/discovery_loop.py
|
|
75
75
|
trilogy/core/processing/discovery_node_factory.py
|
|
76
76
|
trilogy/core/processing/discovery_utility.py
|
|
77
77
|
trilogy/core/processing/discovery_validation.py
|
|
@@ -2,11 +2,11 @@ from trilogy.core.constants import ALL_ROWS_CONCEPT, INTERNAL_NAMESPACE
|
|
|
2
2
|
from trilogy.core.models.build import BuildGrain
|
|
3
3
|
from trilogy.core.models.environment import Environment
|
|
4
4
|
from trilogy.core.processing.concept_strategies_v3 import (
|
|
5
|
-
GroupNode,
|
|
6
5
|
History,
|
|
7
6
|
search_concepts,
|
|
8
7
|
)
|
|
9
8
|
from trilogy.core.processing.node_generators import gen_group_node
|
|
9
|
+
from trilogy.core.processing.nodes import GroupNode
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def test_group_node(test_environment, test_environment_graph):
|
|
@@ -53,7 +53,7 @@ def test_group_node_property(test_environment: Environment, test_environment_gra
|
|
|
53
53
|
}
|
|
54
54
|
assert input_concept_names == {
|
|
55
55
|
"category_name_length",
|
|
56
|
-
"category_name",
|
|
56
|
+
# "category_name",
|
|
57
57
|
"category_id",
|
|
58
58
|
}
|
|
59
59
|
final = group_node.resolve()
|
|
@@ -85,7 +85,7 @@ def test_group_node_property_all(test_environment: Environment, test_environment
|
|
|
85
85
|
}
|
|
86
86
|
assert input_concept_names == {
|
|
87
87
|
"category_name_length",
|
|
88
|
-
"category_name",
|
|
88
|
+
# "category_name",
|
|
89
89
|
"category_id",
|
|
90
90
|
}
|
|
91
91
|
final = group_node.resolve()
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from datetime import date, datetime
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
|
|
4
|
+
from trilogy.core.models.core import (
|
|
5
|
+
ArrayType,
|
|
6
|
+
DataType,
|
|
7
|
+
MapType,
|
|
8
|
+
NumericType,
|
|
9
|
+
StructType,
|
|
10
|
+
TraitDataType,
|
|
11
|
+
)
|
|
12
|
+
from trilogy.core.validation.datasource import type_check
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_type_check():
|
|
16
|
+
# Basic string tests
|
|
17
|
+
assert type_check("hello", DataType.STRING)
|
|
18
|
+
assert not type_check(123, DataType.STRING)
|
|
19
|
+
assert type_check("", DataType.STRING) # empty string
|
|
20
|
+
|
|
21
|
+
# Integer tests
|
|
22
|
+
assert type_check(123, DataType.INTEGER)
|
|
23
|
+
assert type_check(0, DataType.INTEGER)
|
|
24
|
+
assert type_check(-123, DataType.INTEGER)
|
|
25
|
+
assert not type_check("123", DataType.INTEGER)
|
|
26
|
+
assert not type_check(123.0, DataType.INTEGER) # float should not match int
|
|
27
|
+
|
|
28
|
+
# BIGINT tests (same as INTEGER in implementation)
|
|
29
|
+
assert type_check(123, DataType.BIGINT)
|
|
30
|
+
assert type_check(2**63 - 1, DataType.BIGINT) # large integer
|
|
31
|
+
assert not type_check("123", DataType.BIGINT)
|
|
32
|
+
assert not type_check(123.0, DataType.BIGINT)
|
|
33
|
+
|
|
34
|
+
# Float tests
|
|
35
|
+
assert type_check(123.45, DataType.FLOAT)
|
|
36
|
+
assert type_check(123, DataType.FLOAT) # int should match float
|
|
37
|
+
assert type_check(0.0, DataType.FLOAT)
|
|
38
|
+
assert type_check(-123.45, DataType.FLOAT)
|
|
39
|
+
assert not type_check("123.45", DataType.FLOAT)
|
|
40
|
+
|
|
41
|
+
# Decimal support in float
|
|
42
|
+
decimal_val = Decimal("123.45")
|
|
43
|
+
assert type_check(decimal_val, DataType.FLOAT)
|
|
44
|
+
|
|
45
|
+
# NumericType tests
|
|
46
|
+
numeric_type = NumericType() # Assuming NumericType can be instantiated
|
|
47
|
+
assert type_check(123.45, numeric_type)
|
|
48
|
+
assert type_check(123, numeric_type)
|
|
49
|
+
assert type_check(decimal_val, numeric_type)
|
|
50
|
+
assert not type_check("123", numeric_type)
|
|
51
|
+
|
|
52
|
+
# NUMBER and NUMERIC tests (both handle int, float, Decimal)
|
|
53
|
+
assert type_check(123, DataType.NUMBER)
|
|
54
|
+
assert type_check(123.45, DataType.NUMBER)
|
|
55
|
+
assert type_check(decimal_val, DataType.NUMBER)
|
|
56
|
+
assert not type_check("123", DataType.NUMBER)
|
|
57
|
+
|
|
58
|
+
assert type_check(123, DataType.NUMERIC)
|
|
59
|
+
assert type_check(123.45, DataType.NUMERIC)
|
|
60
|
+
assert type_check(decimal_val, DataType.NUMERIC)
|
|
61
|
+
assert not type_check("123", DataType.NUMERIC)
|
|
62
|
+
|
|
63
|
+
# Boolean tests
|
|
64
|
+
assert type_check(True, DataType.BOOL)
|
|
65
|
+
assert type_check(False, DataType.BOOL)
|
|
66
|
+
assert not type_check(1, DataType.BOOL) # int should not match bool
|
|
67
|
+
assert not type_check(0, DataType.BOOL)
|
|
68
|
+
assert not type_check("true", DataType.BOOL)
|
|
69
|
+
|
|
70
|
+
# Date tests
|
|
71
|
+
test_date = date(2023, 12, 25)
|
|
72
|
+
assert type_check(test_date, DataType.DATE)
|
|
73
|
+
assert not type_check("2023-12-25", DataType.DATE)
|
|
74
|
+
assert not type_check(
|
|
75
|
+
datetime.now(), DataType.DATE
|
|
76
|
+
) # datetime should not match date
|
|
77
|
+
|
|
78
|
+
# DateTime and Timestamp tests
|
|
79
|
+
test_datetime = datetime(2023, 12, 25, 15, 30, 45)
|
|
80
|
+
assert type_check(test_datetime, DataType.DATETIME)
|
|
81
|
+
assert type_check(test_datetime, DataType.TIMESTAMP)
|
|
82
|
+
assert not type_check("2023-12-25 15:30:45", DataType.DATETIME)
|
|
83
|
+
assert not type_check(
|
|
84
|
+
test_date, DataType.DATETIME
|
|
85
|
+
) # date should not match datetime
|
|
86
|
+
|
|
87
|
+
# Unix seconds tests
|
|
88
|
+
assert type_check(1640995200, DataType.UNIX_SECONDS) # int timestamp
|
|
89
|
+
assert type_check(1640995200.123, DataType.UNIX_SECONDS) # float timestamp
|
|
90
|
+
assert not type_check("1640995200", DataType.UNIX_SECONDS)
|
|
91
|
+
|
|
92
|
+
# Date part tests
|
|
93
|
+
assert type_check("year", DataType.DATE_PART)
|
|
94
|
+
assert type_check("month", DataType.DATE_PART)
|
|
95
|
+
assert type_check("day", DataType.DATE_PART)
|
|
96
|
+
assert not type_check(2023, DataType.DATE_PART)
|
|
97
|
+
|
|
98
|
+
# Array tests
|
|
99
|
+
assert type_check([1, 2, 3], DataType.ARRAY)
|
|
100
|
+
assert type_check([], DataType.ARRAY) # empty array
|
|
101
|
+
assert type_check(["a", "b"], DataType.ARRAY)
|
|
102
|
+
assert not type_check("not a list", DataType.ARRAY)
|
|
103
|
+
assert not type_check({"key": "value"}, DataType.ARRAY)
|
|
104
|
+
|
|
105
|
+
# ArrayType tests
|
|
106
|
+
array_type = ArrayType(type=DataType.INTEGER)
|
|
107
|
+
assert type_check([1, 2, 3], array_type)
|
|
108
|
+
assert not type_check("not a list", array_type)
|
|
109
|
+
|
|
110
|
+
# Map tests
|
|
111
|
+
assert type_check({"key": "value"}, DataType.MAP)
|
|
112
|
+
assert type_check({}, DataType.MAP) # empty dict
|
|
113
|
+
assert type_check({"a": 1, "b": 2}, DataType.MAP)
|
|
114
|
+
assert not type_check([1, 2, 3], DataType.MAP)
|
|
115
|
+
assert not type_check("not a dict", DataType.MAP)
|
|
116
|
+
|
|
117
|
+
# MapType tests
|
|
118
|
+
map_type = MapType(key_type=DataType.STRING, value_type=DataType.STRING)
|
|
119
|
+
assert type_check({"key": "value"}, map_type)
|
|
120
|
+
assert not type_check([1, 2, 3], map_type)
|
|
121
|
+
|
|
122
|
+
# Struct tests
|
|
123
|
+
assert type_check({"field1": "value1"}, DataType.STRUCT)
|
|
124
|
+
assert type_check({}, DataType.STRUCT)
|
|
125
|
+
assert not type_check([1, 2, 3], DataType.STRUCT)
|
|
126
|
+
|
|
127
|
+
# StructType tests
|
|
128
|
+
struct_type = StructType(
|
|
129
|
+
fields=[DataType.STRING, DataType.STRING],
|
|
130
|
+
fields_map={"field1": DataType.STRING, "field2": DataType.STRING},
|
|
131
|
+
)
|
|
132
|
+
assert not type_check("not a dict", struct_type)
|
|
133
|
+
|
|
134
|
+
# NULL tests
|
|
135
|
+
assert type_check(None, DataType.NULL)
|
|
136
|
+
assert not type_check("", DataType.NULL)
|
|
137
|
+
assert not type_check(0, DataType.NULL)
|
|
138
|
+
assert not type_check(False, DataType.NULL)
|
|
139
|
+
|
|
140
|
+
# UNKNOWN tests (should accept anything)
|
|
141
|
+
assert type_check("anything", DataType.UNKNOWN)
|
|
142
|
+
assert type_check(123, DataType.UNKNOWN)
|
|
143
|
+
assert type_check(None, DataType.UNKNOWN)
|
|
144
|
+
assert type_check([1, 2, 3], DataType.UNKNOWN)
|
|
145
|
+
assert type_check({"key": "value"}, DataType.UNKNOWN)
|
|
146
|
+
|
|
147
|
+
# Nullable tests
|
|
148
|
+
assert type_check(None, DataType.STRING) # nullable by default
|
|
149
|
+
assert type_check(None, DataType.INTEGER)
|
|
150
|
+
assert type_check(None, DataType.FLOAT)
|
|
151
|
+
assert type_check(None, DataType.BOOL)
|
|
152
|
+
|
|
153
|
+
# Non-nullable tests
|
|
154
|
+
assert not type_check(None, DataType.STRING, nullable=False)
|
|
155
|
+
assert not type_check(None, DataType.INTEGER, nullable=False)
|
|
156
|
+
assert not type_check(None, DataType.FLOAT, nullable=False)
|
|
157
|
+
assert not type_check(None, DataType.BOOL, nullable=False)
|
|
158
|
+
|
|
159
|
+
# TraitDataType tests (recursive handling)
|
|
160
|
+
# Assuming TraitDataType wraps another DataType
|
|
161
|
+
trait_string_type = TraitDataType(type=DataType.STRING, traits=[])
|
|
162
|
+
assert type_check("hello", trait_string_type)
|
|
163
|
+
assert not type_check(123, trait_string_type)
|
|
164
|
+
assert type_check(None, trait_string_type) # nullable by default
|
|
165
|
+
assert not type_check(None, trait_string_type, nullable=False)
|
|
166
|
+
|
|
167
|
+
# Nested TraitDataType
|
|
168
|
+
nested_trait_type = TraitDataType(type=DataType.STRING, traits=[])
|
|
169
|
+
assert type_check("hello", nested_trait_type)
|
|
170
|
+
assert not type_check(123, nested_trait_type)
|
|
171
|
+
|
|
172
|
+
# Edge cases and invalid types
|
|
173
|
+
# Test with unsupported/custom types should return False
|
|
174
|
+
class CustomType:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
custom_obj = CustomType()
|
|
178
|
+
# These should all return False as they don't match any known type
|
|
179
|
+
assert not type_check(custom_obj, DataType.STRING)
|
|
180
|
+
assert not type_check(custom_obj, DataType.INTEGER)
|
|
181
|
+
assert not type_check(custom_obj, DataType.ARRAY)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_type_check_edge_cases():
|
|
185
|
+
"""Additional edge case tests"""
|
|
186
|
+
|
|
187
|
+
# Test very large numbers
|
|
188
|
+
large_int = 2**100
|
|
189
|
+
assert type_check(large_int, DataType.INTEGER)
|
|
190
|
+
assert type_check(large_int, DataType.BIGINT)
|
|
191
|
+
assert type_check(large_int, DataType.FLOAT) # int should match float
|
|
192
|
+
|
|
193
|
+
# Test special float values
|
|
194
|
+
assert type_check(float("inf"), DataType.FLOAT)
|
|
195
|
+
assert type_check(float("-inf"), DataType.FLOAT)
|
|
196
|
+
# Note: NaN might need special handling depending on requirements
|
|
197
|
+
# assert type_check(float('nan'), DataType.FLOAT)
|
|
198
|
+
|
|
199
|
+
# Test empty collections
|
|
200
|
+
assert type_check([], DataType.ARRAY)
|
|
201
|
+
assert type_check({}, DataType.MAP)
|
|
202
|
+
assert type_check({}, DataType.STRUCT)
|
|
203
|
+
|
|
204
|
+
# Test nested collections
|
|
205
|
+
nested_list = [[1, 2], [3, 4]]
|
|
206
|
+
nested_dict = {"outer": {"inner": "value"}}
|
|
207
|
+
assert type_check(nested_list, DataType.ARRAY)
|
|
208
|
+
assert type_check(nested_dict, DataType.MAP)
|
|
209
|
+
assert type_check(nested_dict, DataType.STRUCT)
|
|
@@ -69,7 +69,7 @@ class DatasourceColumnBindingData:
|
|
|
69
69
|
actual_modifiers: List[Modifier]
|
|
70
70
|
|
|
71
71
|
def format_failure(self):
|
|
72
|
-
return f"Concept {self.address} value '{self.value}' with type {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
|
|
72
|
+
return f"Concept {self.address} value '{self.value}' with type {self.value_type} and {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
|
|
73
73
|
|
|
74
74
|
def is_modifier_issue(self) -> bool:
|
|
75
75
|
return len(self.value_modifiers) > 0 and any(
|
|
@@ -18,6 +18,7 @@ from trilogy.core.models.author import (
|
|
|
18
18
|
AggregateWrapper,
|
|
19
19
|
Concept,
|
|
20
20
|
ConceptRef,
|
|
21
|
+
Conditional,
|
|
21
22
|
Function,
|
|
22
23
|
Parenthetical,
|
|
23
24
|
UndefinedConcept,
|
|
@@ -129,8 +130,8 @@ def validate_case_output(
|
|
|
129
130
|
def create_struct_output(
|
|
130
131
|
args: list[Any],
|
|
131
132
|
) -> StructType:
|
|
132
|
-
zipped = dict(zip(args[::2], args[
|
|
133
|
-
types = [arg_to_datatype(x) for x in args[
|
|
133
|
+
zipped = dict(zip(args[1::2], args[::2]))
|
|
134
|
+
types = [arg_to_datatype(x) for x in args[::2]]
|
|
134
135
|
return StructType(fields=types, fields_map=zipped)
|
|
135
136
|
|
|
136
137
|
|
|
@@ -997,6 +998,8 @@ def argument_to_purpose(arg) -> Purpose:
|
|
|
997
998
|
return argument_to_purpose(arg.content)
|
|
998
999
|
elif isinstance(arg, WindowItem):
|
|
999
1000
|
return Purpose.PROPERTY
|
|
1001
|
+
elif isinstance(arg, Conditional):
|
|
1002
|
+
return Purpose.PROPERTY
|
|
1000
1003
|
elif isinstance(arg, Concept):
|
|
1001
1004
|
base = arg.purpose
|
|
1002
1005
|
if (
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from abc import ABC
|
|
4
4
|
from collections import UserDict, UserList
|
|
5
5
|
from datetime import date, datetime
|
|
6
|
+
from decimal import Decimal
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from typing import (
|
|
8
9
|
Any,
|
|
@@ -448,6 +449,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
|
|
|
448
449
|
return DataType.STRING
|
|
449
450
|
elif isinstance(arg, float):
|
|
450
451
|
return DataType.FLOAT
|
|
452
|
+
elif isinstance(arg, Decimal):
|
|
453
|
+
return DataType.NUMERIC
|
|
451
454
|
elif isinstance(arg, DataType):
|
|
452
455
|
return arg
|
|
453
456
|
elif isinstance(arg, NumericType):
|
|
@@ -118,19 +118,20 @@ class CTE(BaseModel):
|
|
|
118
118
|
base += f" Source: {self.source.source_type}."
|
|
119
119
|
if self.parent_ctes:
|
|
120
120
|
base += f" References: {', '.join([x.name for x in self.parent_ctes])}."
|
|
121
|
-
if self.joins:
|
|
121
|
+
if self.joins and CONFIG.comments.joins:
|
|
122
122
|
base += f"\n-- Joins: {', '.join([str(x) for x in self.joins])}."
|
|
123
|
-
if self.partial_concepts:
|
|
123
|
+
if self.partial_concepts and CONFIG.comments.partial:
|
|
124
124
|
base += (
|
|
125
125
|
f"\n-- Partials: {', '.join([str(x) for x in self.partial_concepts])}."
|
|
126
126
|
)
|
|
127
|
-
|
|
127
|
+
if CONFIG.comments.source_map:
|
|
128
|
+
base += f"\n-- Source Map: {self.source_map}."
|
|
128
129
|
base += f"\n-- Output: {', '.join([str(x) for x in self.output_columns])}."
|
|
129
130
|
if self.source.input_concepts:
|
|
130
131
|
base += f"\n-- Inputs: {', '.join([str(x) for x in self.source.input_concepts])}."
|
|
131
132
|
if self.hidden_concepts:
|
|
132
133
|
base += f"\n-- Hidden: {', '.join([str(x) for x in self.hidden_concepts])}."
|
|
133
|
-
if self.nullable_concepts:
|
|
134
|
+
if self.nullable_concepts and CONFIG.comments.nullable:
|
|
134
135
|
base += (
|
|
135
136
|
f"\n-- Nullable: {', '.join([str(x) for x in self.nullable_concepts])}."
|
|
136
137
|
)
|
|
@@ -368,6 +369,7 @@ class CTE(BaseModel):
|
|
|
368
369
|
@property
|
|
369
370
|
def group_concepts(self) -> List[BuildConcept]:
|
|
370
371
|
def check_is_not_in_group(c: BuildConcept):
|
|
372
|
+
|
|
371
373
|
if len(self.source_map.get(c.address, [])) > 0:
|
|
372
374
|
return False
|
|
373
375
|
if c.derivation == Derivation.ROWSET:
|
|
@@ -381,8 +383,6 @@ class CTE(BaseModel):
|
|
|
381
383
|
and c.lineage.operator in FunctionClass.AGGREGATE_FUNCTIONS.value
|
|
382
384
|
):
|
|
383
385
|
return True
|
|
384
|
-
if c.purpose == Purpose.METRIC:
|
|
385
|
-
return True
|
|
386
386
|
|
|
387
387
|
if c.derivation == Derivation.BASIC and c.lineage:
|
|
388
388
|
if all([check_is_not_in_group(x) for x in c.lineage.concept_arguments]):
|
|
@@ -392,6 +392,10 @@ class CTE(BaseModel):
|
|
|
392
392
|
and c.lineage.operator == FunctionType.GROUP
|
|
393
393
|
):
|
|
394
394
|
return check_is_not_in_group(c.lineage.concept_arguments[0])
|
|
395
|
+
return False
|
|
396
|
+
if c.purpose == Purpose.METRIC:
|
|
397
|
+
return True
|
|
398
|
+
|
|
395
399
|
return False
|
|
396
400
|
|
|
397
401
|
return (
|
|
@@ -707,6 +711,8 @@ class QueryDatasource(BaseModel):
|
|
|
707
711
|
f" {[c.address for c in self.output_concepts]} concepts and"
|
|
708
712
|
f" {other.name} with {[c.address for c in other.output_concepts]} concepts"
|
|
709
713
|
)
|
|
714
|
+
logger.info(self.source_map)
|
|
715
|
+
logger.info(other.source_map)
|
|
710
716
|
|
|
711
717
|
merged_datasources: dict[str, Union[BuildDatasource, "QueryDatasource"]] = {}
|
|
712
718
|
|
|
@@ -770,6 +776,7 @@ class QueryDatasource(BaseModel):
|
|
|
770
776
|
logger.debug(
|
|
771
777
|
f"[Query Datasource] merged with {[c.address for c in qds.output_concepts]} concepts"
|
|
772
778
|
)
|
|
779
|
+
logger.debug(qds.source_map)
|
|
773
780
|
return qds
|
|
774
781
|
|
|
775
782
|
@property
|
|
@@ -777,7 +784,7 @@ class QueryDatasource(BaseModel):
|
|
|
777
784
|
filters = abs(hash(str(self.condition))) if self.condition else ""
|
|
778
785
|
grain = "_".join([str(c).replace(".", "_") for c in self.grain.components])
|
|
779
786
|
group = ""
|
|
780
|
-
if self.
|
|
787
|
+
if self.group_required:
|
|
781
788
|
keys = [
|
|
782
789
|
x.address for x in self.output_concepts if x.purpose != Purpose.METRIC
|
|
783
790
|
]
|
|
@@ -19,13 +19,13 @@ from trilogy.core.processing.discovery_utility import (
|
|
|
19
19
|
LOGGER_PREFIX,
|
|
20
20
|
depth_to_prefix,
|
|
21
21
|
get_priority_concept,
|
|
22
|
+
group_if_required,
|
|
22
23
|
)
|
|
23
24
|
from trilogy.core.processing.discovery_validation import (
|
|
24
25
|
ValidationResult,
|
|
25
26
|
validate_stack,
|
|
26
27
|
)
|
|
27
28
|
from trilogy.core.processing.nodes import (
|
|
28
|
-
GroupNode,
|
|
29
29
|
History,
|
|
30
30
|
MergeNode,
|
|
31
31
|
StrategyNode,
|
|
@@ -218,6 +218,7 @@ def initialize_loop_context(
|
|
|
218
218
|
f"{depth_to_prefix(depth)}{LOGGER_PREFIX} derived condition row inputs {[x.address for x in required_filters]} present in mandatory list, forcing condition evaluation at this level. "
|
|
219
219
|
)
|
|
220
220
|
mandatory_list = completion_mandatory
|
|
221
|
+
all_mandatory = set(c.address for c in completion_mandatory)
|
|
221
222
|
must_evaluate_condition_on_this_level_not_push_down = True
|
|
222
223
|
else:
|
|
223
224
|
logger.info(
|
|
@@ -263,7 +264,7 @@ def evaluate_loop_conditions(
|
|
|
263
264
|
]
|
|
264
265
|
) and not any(
|
|
265
266
|
[
|
|
266
|
-
x.derivation not in ROOT_DERIVATIONS
|
|
267
|
+
x.derivation not in ROOT_DERIVATIONS + [Derivation.BASIC]
|
|
267
268
|
for x in context.mandatory_list
|
|
268
269
|
if x.address not in context.conditions.row_arguments
|
|
269
270
|
]
|
|
@@ -282,7 +283,7 @@ def evaluate_loop_conditions(
|
|
|
282
283
|
# to ensure filtering happens before something like a SUM
|
|
283
284
|
if (
|
|
284
285
|
context.conditions
|
|
285
|
-
and priority_concept.derivation not in ROOT_DERIVATIONS
|
|
286
|
+
and priority_concept.derivation not in ROOT_DERIVATIONS + [Derivation.BASIC]
|
|
286
287
|
and priority_concept.address not in context.conditions.row_arguments
|
|
287
288
|
):
|
|
288
289
|
logger.info(
|
|
@@ -419,26 +420,9 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
|
|
|
419
420
|
logger.info(
|
|
420
421
|
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Conditions {context.conditions} were injected, checking if we need a group to restore grain"
|
|
421
422
|
)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
downstream_concepts=output.usable_outputs,
|
|
425
|
-
parents=[output.resolve()],
|
|
426
|
-
environment=context.environment,
|
|
427
|
-
depth=context.depth,
|
|
423
|
+
return group_if_required(
|
|
424
|
+
output, context.original_mandatory, context.environment
|
|
428
425
|
)
|
|
429
|
-
if result.required:
|
|
430
|
-
logger.info(
|
|
431
|
-
f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Adding group node with outputs {[x.address for x in context.original_mandatory]}"
|
|
432
|
-
)
|
|
433
|
-
return GroupNode(
|
|
434
|
-
output_concepts=context.original_mandatory,
|
|
435
|
-
input_concepts=output.usable_outputs,
|
|
436
|
-
environment=context.environment,
|
|
437
|
-
parents=[output],
|
|
438
|
-
partial_concepts=output.partial_concepts,
|
|
439
|
-
preexisting_conditions=context.conditions.conditional,
|
|
440
|
-
depth=context.depth,
|
|
441
|
-
)
|
|
442
426
|
return output
|
|
443
427
|
|
|
444
428
|
|
|
@@ -604,18 +588,4 @@ def source_query_concepts(
|
|
|
604
588
|
logger.info(
|
|
605
589
|
f"{depth_to_prefix(0)}{LOGGER_PREFIX} final concepts are {[x.address for x in final]}"
|
|
606
590
|
)
|
|
607
|
-
|
|
608
|
-
downstream_concepts=final,
|
|
609
|
-
parents=[root.resolve()],
|
|
610
|
-
environment=environment,
|
|
611
|
-
).required:
|
|
612
|
-
candidate: StrategyNode = GroupNode(
|
|
613
|
-
output_concepts=final,
|
|
614
|
-
input_concepts=final,
|
|
615
|
-
environment=environment,
|
|
616
|
-
parents=[root],
|
|
617
|
-
partial_concepts=root.partial_concepts,
|
|
618
|
-
)
|
|
619
|
-
else:
|
|
620
|
-
candidate = root
|
|
621
|
-
return candidate
|
|
591
|
+
return group_if_required(root, output_concepts, environment)
|