pytrilogy 0.0.3.97__tar.gz → 0.0.3.98__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.

Files changed (158) hide show
  1. {pytrilogy-0.0.3.97/pytrilogy.egg-info → pytrilogy-0.0.3.98}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/functions.py +1 -1
  6. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/execute.py +0 -1
  7. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/node_merge_node.py +109 -5
  8. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/validation/concept.py +9 -0
  9. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/validation/environment.py +1 -0
  10. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/common.py +2 -10
  11. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/metadata.py +1 -1
  12. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/executor.py +7 -0
  13. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/parse_engine.py +2 -2
  14. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/render.py +2 -2
  15. pytrilogy-0.0.3.98/trilogy/std/metric.preql +15 -0
  16. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/LICENSE.md +0 -0
  17. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/README.md +0 -0
  18. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/pyproject.toml +0 -0
  19. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/pytrilogy.egg-info/dependency_links.txt +0 -0
  20. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/pytrilogy.egg-info/entry_points.txt +0 -0
  21. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/pytrilogy.egg-info/requires.txt +0 -0
  22. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/pytrilogy.egg-info/top_level.txt +0 -0
  23. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/setup.cfg +0 -0
  24. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/setup.py +0 -0
  25. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_datatypes.py +0 -0
  26. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_declarations.py +0 -0
  27. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_derived_concepts.py +0 -0
  28. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_discovery_nodes.py +0 -0
  29. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_enums.py +0 -0
  30. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_environment.py +0 -0
  31. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_execute_models.py +0 -0
  32. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_executor.py +0 -0
  33. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_failure.py +0 -0
  34. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_functions.py +0 -0
  35. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_imports.py +0 -0
  36. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_metadata.py +0 -0
  37. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_models.py +0 -0
  38. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_multi_join_assignments.py +0 -0
  39. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_parse_engine.py +0 -0
  40. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_parsing.py +0 -0
  41. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_parsing_failures.py +0 -0
  42. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_partial_handling.py +0 -0
  43. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_query_processing.py +0 -0
  44. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_query_render.py +0 -0
  45. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_select.py +0 -0
  46. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_show.py +0 -0
  47. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_statements.py +0 -0
  48. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_typing.py +0 -0
  49. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_undefined_concept.py +0 -0
  50. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_user_functions.py +0 -0
  51. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/tests/test_where_clause.py +0 -0
  52. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/authoring/__init__.py +0 -0
  53. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/constants.py +0 -0
  54. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/__init__.py +0 -0
  55. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/constants.py +0 -0
  56. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/enums.py +0 -0
  57. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/env_processor.py +0 -0
  58. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/environment_helpers.py +0 -0
  59. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/ergonomics.py +0 -0
  60. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/exceptions.py +0 -0
  61. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/graph_models.py +0 -0
  62. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/internal.py +0 -0
  63. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/__init__.py +0 -0
  64. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/author.py +0 -0
  65. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/build.py +0 -0
  66. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/build_environment.py +0 -0
  67. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/core.py +0 -0
  68. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/datasource.py +0 -0
  69. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/models/environment.py +0 -0
  70. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/optimization.py +0 -0
  71. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/optimizations/__init__.py +0 -0
  72. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/optimizations/base_optimization.py +0 -0
  73. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/optimizations/inline_datasource.py +0 -0
  74. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  75. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/__init__.py +0 -0
  76. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  77. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/discovery_loop.py +0 -0
  78. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/discovery_node_factory.py +0 -0
  79. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/discovery_utility.py +0 -0
  80. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/discovery_validation.py +0 -0
  81. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/graph_utils.py +0 -0
  82. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/__init__.py +0 -0
  83. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  84. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/common.py +0 -0
  85. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  86. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  87. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/group_node.py +0 -0
  88. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  89. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  90. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  91. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  92. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  93. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  94. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  95. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/select_node.py +0 -0
  96. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  97. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/union_node.py +0 -0
  98. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  99. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/node_generators/window_node.py +0 -0
  100. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/__init__.py +0 -0
  101. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/base_node.py +0 -0
  102. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/filter_node.py +0 -0
  103. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/group_node.py +0 -0
  104. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/merge_node.py +0 -0
  105. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  106. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  107. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/union_node.py +0 -0
  108. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  109. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/nodes/window_node.py +0 -0
  110. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/processing/utility.py +0 -0
  111. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/query_processor.py +0 -0
  112. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/statements/__init__.py +0 -0
  113. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/statements/author.py +0 -0
  114. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/statements/build.py +0 -0
  115. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/statements/common.py +0 -0
  116. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/statements/execute.py +0 -0
  117. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/utility.py +0 -0
  118. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/validation/__init__.py +0 -0
  119. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/validation/common.py +0 -0
  120. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/validation/datasource.py +0 -0
  121. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/core/validation/fix.py +0 -0
  122. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/__init__.py +0 -0
  123. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/base.py +0 -0
  124. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/bigquery.py +0 -0
  125. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/config.py +0 -0
  126. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/dataframe.py +0 -0
  127. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/duckdb.py +0 -0
  128. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/enums.py +0 -0
  129. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/postgres.py +0 -0
  130. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/presto.py +0 -0
  131. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/snowflake.py +0 -0
  132. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/dialect/sql_server.py +0 -0
  133. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/engine.py +0 -0
  134. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/hooks/__init__.py +0 -0
  135. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/hooks/base_hook.py +0 -0
  136. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/hooks/graph_hook.py +0 -0
  137. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/hooks/query_debugger.py +0 -0
  138. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/metadata/__init__.py +0 -0
  139. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parser.py +0 -0
  140. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/__init__.py +0 -0
  141. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/common.py +0 -0
  142. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/config.py +0 -0
  143. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/exceptions.py +0 -0
  144. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/helpers.py +0 -0
  145. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/parsing/trilogy.lark +0 -0
  146. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/py.typed +0 -0
  147. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/render.py +0 -0
  148. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/scripts/__init__.py +0 -0
  149. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/scripts/trilogy.py +0 -0
  150. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/__init__.py +0 -0
  151. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/date.preql +0 -0
  152. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/display.preql +0 -0
  153. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/geography.preql +0 -0
  154. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/money.preql +0 -0
  155. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/net.preql +0 -0
  156. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/ranking.preql +0 -0
  157. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/std/report.preql +0 -0
  158. {pytrilogy-0.0.3.97 → pytrilogy-0.0.3.98}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.97
3
+ Version: 0.0.3.98
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.97
3
+ Version: 0.0.3.98
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -149,6 +149,7 @@ trilogy/std/__init__.py
149
149
  trilogy/std/date.preql
150
150
  trilogy/std/display.preql
151
151
  trilogy/std/geography.preql
152
+ trilogy/std/metric.preql
152
153
  trilogy/std/money.preql
153
154
  trilogy/std/net.preql
154
155
  trilogy/std/ranking.preql
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.97"
7
+ __version__ = "0.0.3.98"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -427,7 +427,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
427
427
  {DataType.STRING},
428
428
  ],
429
429
  output_purpose=Purpose.PROPERTY,
430
- output_type=DataType.BOOL,
430
+ output_type=DataType.STRING,
431
431
  arg_count=2,
432
432
  ),
433
433
  FunctionType.SUBSTRING: FunctionConfig(
@@ -151,7 +151,6 @@ class CTE(BaseModel):
151
151
  ]
152
152
  ):
153
153
  return False
154
-
155
154
  self.source.datasources = [
156
155
  ds_being_inlined,
157
156
  *[
@@ -1,4 +1,5 @@
1
- from typing import List, Optional
1
+ from itertools import combinations
2
+ from typing import Callable, List, Optional
2
3
 
3
4
  import networkx as nx
4
5
  from networkx.algorithms import approximation as ax
@@ -11,7 +12,12 @@ from trilogy.core.graph_models import (
11
12
  concept_to_node,
12
13
  prune_sources_for_conditions,
13
14
  )
14
- from trilogy.core.models.build import BuildConcept, BuildConditional, BuildWhereClause
15
+ from trilogy.core.models.build import (
16
+ BuildConcept,
17
+ BuildConditional,
18
+ BuildGrain,
19
+ BuildWhereClause,
20
+ )
15
21
  from trilogy.core.models.build_environment import BuildEnvironment
16
22
  from trilogy.core.processing.nodes import History, MergeNode, StrategyNode
17
23
  from trilogy.core.processing.utility import padding
@@ -29,7 +35,10 @@ def filter_pseudonyms_for_source(
29
35
  if edge in pseudonyms:
30
36
  lengths = {}
31
37
  for n in edge:
32
- lengths[n] = nx.shortest_path_length(ds_graph, node, n)
38
+ try:
39
+ lengths[n] = nx.shortest_path_length(ds_graph, node, n)
40
+ except nx.NetworkXNoPath:
41
+ lengths[n] = 999
33
42
  to_remove.add(max(lengths, key=lambda x: lengths.get(x, 0)))
34
43
  for node in to_remove:
35
44
  ds_graph.remove_node(node)
@@ -84,12 +93,104 @@ def extract_ds_components(
84
93
  return graphs
85
94
 
86
95
 
96
+ def prune_and_merge(
97
+ G: nx.DiGraph,
98
+ keep_node_lambda: Callable[[str], bool],
99
+ ) -> nx.DiGraph:
100
+ """
101
+ Prune nodes of one type and create direct connections between remaining nodes.
102
+
103
+ Args:
104
+ G: NetworkX graph
105
+ keep_node_type: The node type to keep
106
+ node_type_attr: Attribute name that stores node type
107
+
108
+ Returns:
109
+ New graph with only keep_node_type nodes and merged connections
110
+ """
111
+ # Get nodes to keep
112
+ nodes_to_keep = [n for n in G.nodes if keep_node_lambda(n)]
113
+ # Create new graph
114
+ new_graph = G.subgraph(nodes_to_keep).copy()
115
+
116
+ # Find paths between nodes to keep through removed nodes
117
+ nodes_to_remove = [n for n in G.nodes() if n not in nodes_to_keep]
118
+
119
+ for node_pair in combinations(nodes_to_keep, 2):
120
+ n1, n2 = node_pair
121
+
122
+ # Check if there's a path through removed nodes
123
+ try:
124
+ path = nx.shortest_path(G, n1, n2)
125
+ # If path exists and goes through nodes we're removing
126
+ if len(path) > 2 or any(node in nodes_to_remove for node in path[1:-1]):
127
+ new_graph.add_edge(n1, n2)
128
+ except nx.NetworkXNoPath:
129
+ continue
130
+
131
+ return new_graph
132
+
133
+
134
+ def reinject_common_join_keys_v2(
135
+ G: ReferenceGraph,
136
+ final: nx.DiGraph,
137
+ nodelist: list[str],
138
+ synonyms: set[str] = set(),
139
+ ) -> bool:
140
+ # when we've discovered a concept join, for each pair of ds nodes
141
+ # check if they have more keys in common
142
+ # and inject those to discovery as join conditions
143
+ def is_ds_node(n: str) -> bool:
144
+ return n.startswith("ds~")
145
+
146
+ ds_graph = prune_and_merge(final, is_ds_node)
147
+ injected = False
148
+ for datasource in ds_graph.nodes:
149
+ node1 = G.datasources[datasource]
150
+ neighbors = nx.all_neighbors(ds_graph, datasource)
151
+ for neighbor in neighbors:
152
+ node2 = G.datasources[neighbor]
153
+ common_concepts = set(
154
+ x.concept.address for x in node1.columns
155
+ ).intersection(set(x.concept.address for x in node2.columns))
156
+ concrete_concepts = [
157
+ x.concept for x in node1.columns if x.concept.address in common_concepts
158
+ ]
159
+ reduced = BuildGrain.from_concepts(concrete_concepts).components
160
+ existing_addresses = set()
161
+ for concrete in concrete_concepts:
162
+ logger.info(
163
+ f"looking at column {concrete.address} with pseudonyms {concrete.pseudonyms}"
164
+ )
165
+ cnode = concept_to_node(concrete.with_default_grain())
166
+ if cnode in final.nodes:
167
+ existing_addresses.add(concrete.address)
168
+ continue
169
+ for concrete in concrete_concepts:
170
+ if concrete.address in synonyms:
171
+ continue
172
+ if concrete.address not in reduced:
173
+ continue
174
+ # skip anything that is already in the graph pseudonyms
175
+ if any(x in concrete.pseudonyms for x in existing_addresses):
176
+ continue
177
+ cnode = concept_to_node(concrete.with_default_grain())
178
+ final.add_edge(datasource, cnode)
179
+ final.add_edge(neighbor, cnode)
180
+ logger.info(
181
+ f"{LOGGER_PREFIX} reinjecting common join key {cnode} between {datasource} and {neighbor}"
182
+ )
183
+ injected = True
184
+ return injected
185
+
186
+
87
187
  def determine_induced_minimal_nodes(
88
188
  G: ReferenceGraph,
89
189
  nodelist: list[str],
90
190
  environment: BuildEnvironment,
91
191
  filter_downstream: bool,
92
192
  accept_partial: bool = False,
193
+ synonyms: set[str] = set(),
93
194
  ) -> nx.DiGraph | None:
94
195
  H: nx.Graph = nx.to_undirected(G).copy()
95
196
  nodes_to_remove = []
@@ -129,7 +230,7 @@ def determine_induced_minimal_nodes(
129
230
  return None
130
231
  path_removals = list(x for x in H.nodes if x not in paths)
131
232
  if path_removals:
132
- logger.debug(f"Removing paths {path_removals} from graph")
233
+ # logger.debug(f"Removing paths {path_removals} from graph")
133
234
  H.remove_nodes_from(path_removals)
134
235
  # logger.debug(f"Graph after path removal {H.nodes}")
135
236
  sG: nx.Graph = ax.steinertree.steiner_tree(H, nodelist).copy()
@@ -148,8 +249,10 @@ def determine_induced_minimal_nodes(
148
249
  if not accept_partial:
149
250
  continue
150
251
  final.add_edge(*edge)
151
- # all concept nodes must have a parent
152
252
 
253
+ reinject_common_join_keys_v2(G, final, nodelist, synonyms)
254
+
255
+ # all concept nodes must have a parent
153
256
  if not all(
154
257
  [
155
258
  final.in_degree(node) > 0
@@ -302,6 +405,7 @@ def resolve_weak_components(
302
405
  filter_downstream=filter_downstream,
303
406
  accept_partial=accept_partial,
304
407
  environment=environment,
408
+ synonyms=synonyms,
305
409
  )
306
410
 
307
411
  if not g or not g.nodes:
@@ -26,6 +26,15 @@ def validate_key_concept(
26
26
  ):
27
27
  results: list[ValidationTest] = []
28
28
  seen: dict[str, int] = {}
29
+
30
+ count = 0
31
+ for datasource in build_env.datasources.values():
32
+ if concept.address in [c.address for c in datasource.concepts]:
33
+ count += 1
34
+ # if it only has one source, it's a key
35
+ if count <= 1:
36
+ return results
37
+
29
38
  for datasource in build_env.datasources.values():
30
39
  if concept.address in [c.address for c in datasource.concepts]:
31
40
  assignment = [
@@ -69,4 +69,5 @@ def validate_environment(
69
69
  f"Environment validation failed with the following errors:\n{messages}",
70
70
  children=exceptions,
71
71
  )
72
+
72
73
  return results
@@ -10,7 +10,6 @@ from trilogy.core.models.build import (
10
10
  BuildParamaterizedConceptReference,
11
11
  BuildParenthetical,
12
12
  )
13
- from trilogy.core.models.datasource import RawColumnExpr
14
13
  from trilogy.core.models.execute import (
15
14
  CTE,
16
15
  InstantiatedUnnestJoin,
@@ -65,15 +64,8 @@ def render_join_concept(
65
64
  inlined_ctes: set[str],
66
65
  ):
67
66
  if cte.name in inlined_ctes:
68
- ds = cte.source.datasources[0]
69
- raw_content = ds.get_alias(concept)
70
- if isinstance(raw_content, RawColumnExpr):
71
- rval = raw_content.text
72
- return rval
73
- elif isinstance(raw_content, BuildFunction):
74
- rval = render_expr(raw_content, cte=cte)
75
- return rval
76
- return f"{quote_character}{name}{quote_character}.{quote_character}{raw_content}{quote_character}"
67
+ base = render_expr(concept, cte)
68
+ return base
77
69
  return f"{quote_character}{name}{quote_character}.{quote_character}{concept.safe_address}{quote_character}"
78
70
 
79
71
 
@@ -174,7 +174,7 @@ def raw_validation_to_result(
174
174
  ) -> Optional[MockResult]:
175
175
  """Convert raw validation tests to mock result."""
176
176
  if not raw:
177
- return None
177
+ return MockResult([], ["check_type", "expected", "result", "ran", "query"])
178
178
  output = []
179
179
  for row in raw:
180
180
  if row.raw_query and generator and not row.generated_query:
@@ -410,6 +410,13 @@ class Executor(object):
410
410
  )
411
411
  output.extend(results)
412
412
  continue
413
+ elif isinstance(statement, ProcessedValidateStatement):
414
+ validate_result = handle_processed_validate_statement(
415
+ statement, self.generator, self.validate_environment
416
+ )
417
+ if validate_result:
418
+ output.append(validate_result)
419
+ continue
413
420
  if non_interactive:
414
421
  if not isinstance(
415
422
  statement, (ProcessedCopyStatement, ProcessedQueryPersist)
@@ -1002,7 +1002,7 @@ class ParseToObjects(Transformer):
1002
1002
  def validate_statement(self, meta: Meta, args) -> ValidateStatement:
1003
1003
  if len(args) == 2:
1004
1004
  scope = args[0]
1005
- targets = args[1]
1005
+ targets = args[1].split(",")
1006
1006
  elif len(args) == 0:
1007
1007
  scope = ValidationScope.ALL
1008
1008
  targets = None
@@ -2295,7 +2295,7 @@ def parse_text(
2295
2295
  raise _create_syntax_error(210, pos, text)
2296
2296
 
2297
2297
  # Handle FROM token error
2298
- parsed_tokens = [x.value for x in e.token_history] if e.token_history else []
2298
+ parsed_tokens = [x.value for x in e.token_history if x] if e.token_history else []
2299
2299
  if parsed_tokens == ["FROM"]:
2300
2300
  raise _create_syntax_error(101, pos, text)
2301
2301
 
@@ -546,8 +546,8 @@ class Renderer:
546
546
  return f"{self.to_string(arg.arguments[0])}[{self.to_string(arg.arguments[1])}]"
547
547
 
548
548
  if arg.operator == FunctionType.CASE:
549
- inputs = "\n".join(args)
550
- return f"CASE {inputs}\nEND"
549
+ inputs = "\n\t".join(args)
550
+ return f"CASE\n\t{inputs}\nEND"
551
551
  return f"{arg.operator.value}({inputs})"
552
552
 
553
553
  @to_string.register
@@ -0,0 +1,15 @@
1
+ # Length and distance units
2
+ type m numeric; # meters
3
+ type km numeric; # kilometers
4
+ type cm numeric; # centimeters
5
+ type mm numeric; # millimeters
6
+
7
+ # Mass units
8
+ type kg numeric; # kilograms
9
+ type g numeric; # grams
10
+ type tonne numeric; # metric tons (1000 kg)
11
+
12
+ # Force units
13
+ type n numeric; # newtons
14
+ type kn numeric; # kilonewtons (1000 N)
15
+ type mn numeric; # meganewtons (1,000,000 N)
File without changes
File without changes
File without changes
File without changes