pytrilogy 0.0.3.56__tar.gz → 0.0.3.57__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 (148) hide show
  1. {pytrilogy-0.0.3.56/pytrilogy.egg-info → pytrilogy-0.0.3.57}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_discovery_nodes.py +10 -2
  4. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_functions.py +17 -0
  5. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_user_functions.py +2 -0
  6. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/__init__.py +1 -1
  7. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/authoring/__init__.py +12 -1
  8. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/execute.py +4 -1
  9. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/optimization.py +4 -4
  10. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/concept_strategies_v3.py +4 -3
  11. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/discovery_node_factory.py +8 -2
  12. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/basic_node.py +29 -11
  13. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/node_merge_node.py +1 -1
  14. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/synonym_node.py +2 -1
  15. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/unnest_node.py +7 -1
  16. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/base_node.py +0 -13
  17. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/group_node.py +1 -1
  18. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/utility.py +38 -11
  19. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/query_processor.py +3 -3
  20. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/statements/author.py +6 -2
  21. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/statements/execute.py +3 -2
  22. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/base.py +3 -30
  23. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/executor.py +13 -4
  24. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/parse_engine.py +8 -2
  25. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/trilogy.lark +1 -1
  26. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/LICENSE.md +0 -0
  27. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/README.md +0 -0
  28. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/pyproject.toml +0 -0
  29. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/pytrilogy.egg-info/SOURCES.txt +0 -0
  30. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/pytrilogy.egg-info/dependency_links.txt +0 -0
  31. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/pytrilogy.egg-info/entry_points.txt +0 -0
  32. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/pytrilogy.egg-info/requires.txt +0 -0
  33. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/pytrilogy.egg-info/top_level.txt +0 -0
  34. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/setup.cfg +0 -0
  35. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/setup.py +0 -0
  36. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_datatypes.py +0 -0
  37. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_declarations.py +0 -0
  38. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_derived_concepts.py +0 -0
  39. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_enums.py +0 -0
  40. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_environment.py +0 -0
  41. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_executor.py +0 -0
  42. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_failure.py +0 -0
  43. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_imports.py +0 -0
  44. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_metadata.py +0 -0
  45. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_models.py +0 -0
  46. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_multi_join_assignments.py +0 -0
  47. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_parse_engine.py +0 -0
  48. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_parsing.py +0 -0
  49. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_parsing_failures.py +0 -0
  50. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_partial_handling.py +0 -0
  51. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_query_processing.py +0 -0
  52. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_query_render.py +0 -0
  53. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_select.py +0 -0
  54. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_show.py +0 -0
  55. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_statements.py +0 -0
  56. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_typing.py +0 -0
  57. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_undefined_concept.py +0 -0
  58. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/tests/test_where_clause.py +0 -0
  59. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/compiler.py +0 -0
  60. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/constants.py +0 -0
  61. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/__init__.py +0 -0
  62. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/constants.py +0 -0
  63. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/enums.py +0 -0
  64. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/env_processor.py +0 -0
  65. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/environment_helpers.py +0 -0
  66. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/ergonomics.py +0 -0
  67. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/exceptions.py +0 -0
  68. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/functions.py +0 -0
  69. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/graph_models.py +0 -0
  70. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/internal.py +0 -0
  71. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/__init__.py +0 -0
  72. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/author.py +0 -0
  73. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/build.py +0 -0
  74. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/build_environment.py +0 -0
  75. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/core.py +0 -0
  76. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/datasource.py +0 -0
  77. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/models/environment.py +0 -0
  78. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/optimizations/__init__.py +0 -0
  79. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/optimizations/base_optimization.py +0 -0
  80. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/optimizations/inline_datasource.py +0 -0
  81. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  82. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/__init__.py +0 -0
  83. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/discovery_loop.py +0 -0
  84. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/discovery_utility.py +0 -0
  85. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/discovery_validation.py +0 -0
  86. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/graph_utils.py +0 -0
  87. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/__init__.py +0 -0
  88. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/common.py +0 -0
  89. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  90. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/group_node.py +0 -0
  91. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  92. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  93. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  94. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  95. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  96. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  97. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  98. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/select_node.py +0 -0
  99. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/union_node.py +0 -0
  100. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/node_generators/window_node.py +0 -0
  101. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/__init__.py +0 -0
  102. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/filter_node.py +0 -0
  103. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/merge_node.py +0 -0
  104. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  105. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  106. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/union_node.py +0 -0
  107. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  108. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/processing/nodes/window_node.py +0 -0
  109. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/statements/__init__.py +0 -0
  110. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/statements/build.py +0 -0
  111. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/statements/common.py +0 -0
  112. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/core/utility.py +0 -0
  113. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/__init__.py +0 -0
  114. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/bigquery.py +0 -0
  115. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/common.py +0 -0
  116. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/config.py +0 -0
  117. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/dataframe.py +0 -0
  118. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/duckdb.py +0 -0
  119. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/enums.py +0 -0
  120. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/postgres.py +0 -0
  121. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/presto.py +0 -0
  122. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/snowflake.py +0 -0
  123. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/dialect/sql_server.py +0 -0
  124. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/engine.py +0 -0
  125. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/hooks/__init__.py +0 -0
  126. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/hooks/base_hook.py +0 -0
  127. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/hooks/graph_hook.py +0 -0
  128. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/hooks/query_debugger.py +0 -0
  129. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/metadata/__init__.py +0 -0
  130. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parser.py +0 -0
  131. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/__init__.py +0 -0
  132. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/common.py +0 -0
  133. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/config.py +0 -0
  134. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/exceptions.py +0 -0
  135. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/helpers.py +0 -0
  136. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/parsing/render.py +0 -0
  137. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/py.typed +0 -0
  138. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/render.py +0 -0
  139. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/scripts/__init__.py +0 -0
  140. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/scripts/trilogy.py +0 -0
  141. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/std/__init__.py +0 -0
  142. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/std/date.preql +0 -0
  143. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/std/display.preql +0 -0
  144. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/std/geography.preql +0 -0
  145. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/std/money.preql +0 -0
  146. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/std/net.preql +0 -0
  147. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/std/report.preql +0 -0
  148. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.57}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.56
3
+ Version: 0.0.3.57
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.56
3
+ Version: 0.0.3.57
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -51,7 +51,11 @@ def test_group_node_property(test_environment: Environment, test_environment_gra
51
51
  for x in group_node.parents[0].output_concepts
52
52
  if x not in group_node.parents[0].hidden_concepts
53
53
  }
54
- assert input_concept_names == {"category_name_length", "category_id"}
54
+ assert input_concept_names == {
55
+ "category_name_length",
56
+ "category_name",
57
+ "category_id",
58
+ }
55
59
  final = group_node.resolve()
56
60
  assert len(final.datasources) == 1
57
61
  assert final.datasources[0].group_required is False
@@ -79,7 +83,11 @@ def test_group_node_property_all(test_environment: Environment, test_environment
79
83
  for x in group_node.parents[0].output_concepts
80
84
  if x not in group_node.parents[0].hidden_concepts
81
85
  }
82
- assert input_concept_names == {"category_name_length", "category_id"}
86
+ assert input_concept_names == {
87
+ "category_name_length",
88
+ "category_name",
89
+ "category_id",
90
+ }
83
91
  final = group_node.resolve()
84
92
  assert len(final.datasources) == 1
85
93
  assert final.datasources[0].group_required is False
@@ -36,6 +36,23 @@ def test_typing():
36
36
  assert x in set([x])
37
37
 
38
38
 
39
+ def test_negative_float_parsing(test_environment):
40
+ declarations = """
41
+
42
+ const x <- -1.23;
43
+
44
+ select
45
+ x
46
+ where x >-1.24;
47
+
48
+ """
49
+ env, parsed = parse(declarations, environment=test_environment)
50
+ select: SelectStatement = parsed[-1]
51
+
52
+ for dialect in TEST_DIALECTS:
53
+ dialect.compile_statement(process_query(test_environment, select, hooks=[]))
54
+
55
+
39
56
  def test_functions(test_environment):
40
57
  declarations = """
41
58
 
@@ -208,7 +208,9 @@ auto test <-SUM(CASE WHEN 10 = weekday THEN x ELSE 0 END) +
208
208
  def test_user_function_import():
209
209
  env = Environment(working_path=Path(__file__).parent)
210
210
  x = Dialects.DUCK_DB.default_executor(environment=env)
211
+ from trilogy.hooks import DebuggingHook
211
212
 
213
+ DebuggingHook()
212
214
  results = x.execute_query(
213
215
  """
214
216
  import test_env_functions as test_env_functions;
@@ -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.56"
7
+ __version__ = "0.0.3.57"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -43,14 +43,19 @@ from trilogy.core.models.core import (
43
43
  MapType,
44
44
  StructType,
45
45
  )
46
- from trilogy.core.models.datasource import Datasource, DatasourceMetadata
46
+ from trilogy.core.models.datasource import Address, Datasource, DatasourceMetadata
47
47
  from trilogy.core.models.environment import Environment
48
48
  from trilogy.core.statements.author import (
49
49
  ConceptDeclarationStatement,
50
50
  ConceptTransform,
51
+ CopyStatement,
52
+ Grain,
53
+ HasUUID,
54
+ ImportStatement,
51
55
  MultiSelectStatement,
52
56
  PersistStatement,
53
57
  RawSQLStatement,
58
+ RowsetDerivationStatement,
54
59
  SelectItem,
55
60
  SelectStatement,
56
61
  )
@@ -73,6 +78,8 @@ __all__ = [
73
78
  "DataType",
74
79
  "StructType",
75
80
  "ListType",
81
+ "Grain",
82
+ "RowsetDerivationStatement",
76
83
  "MapType",
77
84
  "ListWrapper",
78
85
  "FunctionType",
@@ -109,4 +116,8 @@ __all__ = [
109
116
  "MultiSelectLineage",
110
117
  "RowsetItem",
111
118
  "FunctionCallWrapper",
119
+ "CopyStatement",
120
+ "HasUUID",
121
+ "ImportStatement",
122
+ "Address",
112
123
  ]
@@ -658,18 +658,21 @@ class QueryDatasource(BaseModel):
658
658
  @classmethod
659
659
  def validate_source_map(cls, v: dict, info: ValidationInfo):
660
660
  values = info.data
661
+ hidden_concepts = values.get("hidden_concepts", set())
661
662
  for key in ("input_concepts", "output_concepts"):
662
663
  if not values.get(key):
663
664
  continue
664
665
  concept: BuildConcept
665
666
  for concept in values[key]:
667
+ if concept.address in hidden_concepts:
668
+ continue
666
669
  if (
667
670
  concept.address not in v
668
671
  and not any(x in v for x in concept.pseudonyms)
669
672
  and CONFIG.validate_missing
670
673
  ):
671
674
  raise SyntaxError(
672
- f"On query datasource missing source map for {concept.address} on {key} with pseudonyms {concept.pseudonyms}, have {v}"
675
+ f"On query datasource from {values} missing source map entry (map: {v}) for {concept.address} on {key} with pseudonyms {concept.pseudonyms}, have {v}"
673
676
  )
674
677
  return v
675
678
 
@@ -141,9 +141,7 @@ def is_direct_return_eligible(cte: CTE | UnionCTE) -> CTE | UnionCTE | None:
141
141
  if not output_addresses.issubset(parent_output_addresses):
142
142
  return None
143
143
  if not direct_parent.grain == cte.grain:
144
- logger.info("grain mismatch, cannot early exit")
145
- logger.info(direct_parent.grain)
146
- logger.info(cte.grain)
144
+ logger.info("[Direct Return] grain mismatch, cannot early exit")
147
145
  return None
148
146
 
149
147
  assert isinstance(cte, CTE)
@@ -236,6 +234,8 @@ def optimize_ctes(
236
234
  complete = not actions_taken
237
235
  loops += 1
238
236
  input = reorder_ctes(filter_irrelevant_ctes(input, root_cte))
239
- logger.info(f"finished checking for {type(rule).__name__} in {loops} loops")
237
+ logger.info(
238
+ f"[Optimization] Finished checking for {type(rule).__name__} after {loops} loop(s)"
239
+ )
240
240
 
241
241
  return reorder_ctes(filter_irrelevant_ctes(input, root_cte))
@@ -408,8 +408,9 @@ def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
408
408
  logger.info(
409
409
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Conditions {context.conditions} were injected, checking if we need a group to restore grain"
410
410
  )
411
+
411
412
  result = GroupNode.check_if_required(
412
- downstream_concepts=context.original_mandatory,
413
+ downstream_concepts=output.usable_outputs,
413
414
  parents=[output.resolve()],
414
415
  environment=context.environment,
415
416
  depth=context.depth,
@@ -420,7 +421,7 @@ def generate_loop_completion(context: LoopContext, virtual) -> StrategyNode:
420
421
  )
421
422
  return GroupNode(
422
423
  output_concepts=context.original_mandatory,
423
- input_concepts=context.original_mandatory,
424
+ input_concepts=output.usable_outputs,
424
425
  environment=context.environment,
425
426
  parents=[output],
426
427
  partial_concepts=output.partial_concepts,
@@ -504,7 +505,7 @@ def _search_concepts(
504
505
  conditions=context.conditions,
505
506
  accept_partial=accept_partial,
506
507
  )
507
- # assig
508
+ # assign
508
509
  context.found = found_c
509
510
  early_exit = check_for_early_exit(complete, partial, context, priority_concept)
510
511
  if early_exit:
@@ -307,12 +307,18 @@ class RootNodeHandler:
307
307
  def _resolve_root_concepts(
308
308
  self, root_targets: List[BuildConcept]
309
309
  ) -> Optional[StrategyNode]:
310
-
310
+ synonym_node = self._try_synonym_resolution(root_targets)
311
+ if synonym_node:
312
+ logger.info(
313
+ f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
314
+ f"resolved root concepts through synonyms"
315
+ )
316
+ return synonym_node
311
317
  expanded_node = self._try_merge_expansion(root_targets)
312
318
  if expanded_node:
313
319
  return expanded_node
314
320
 
315
- return self._try_synonym_resolution(root_targets)
321
+ return None
316
322
 
317
323
  def _try_merge_expansion(
318
324
  self, root_targets: List[BuildConcept]
@@ -1,7 +1,7 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.enums import FunctionClass, SourceType
4
+ from trilogy.core.enums import FunctionClass, FunctionType, SourceType
5
5
  from trilogy.core.models.build import BuildConcept, BuildFunction, BuildWhereClause
6
6
  from trilogy.core.models.build_environment import BuildEnvironment
7
7
  from trilogy.core.processing.node_generators.common import (
@@ -47,13 +47,25 @@ def gen_basic_node(
47
47
  logger.info(
48
48
  f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} with lineage {concept.lineage} has parents {[x for x in parent_concepts]}"
49
49
  )
50
-
50
+ synonyms: list[BuildConcept] = []
51
+ ignored_optional: set[str] = set()
52
+ assert isinstance(concept.lineage, BuildFunction)
53
+ if concept.lineage.operator == FunctionType.ATTR_ACCESS:
54
+ logger.info(
55
+ f"{depth_prefix}{LOGGER_PREFIX} checking for synonyms for attribute access"
56
+ )
57
+ for x in local_optional:
58
+ for z in x.pseudonyms:
59
+ s_concept = environment.alias_origin_lookup[z]
60
+ if is_equivalent_basic_function_lineage(concept, s_concept):
61
+ synonyms.append(s_concept)
62
+ ignored_optional.add(x.address)
51
63
  equivalent_optional = [
52
64
  x
53
65
  for x in local_optional
54
66
  if is_equivalent_basic_function_lineage(concept, x)
55
67
  and x.address != concept.address
56
- ]
68
+ ] + synonyms
57
69
 
58
70
  if equivalent_optional:
59
71
  logger.info(
@@ -66,6 +78,7 @@ def gen_basic_node(
66
78
  for x in local_optional
67
79
  if x not in equivalent_optional
68
80
  and not any(x.address in y.pseudonyms for y in equivalent_optional)
81
+ and x.address not in ignored_optional
69
82
  ]
70
83
  all_parents: list[BuildConcept] = unique(
71
84
  parent_concepts + non_equivalent_optional, "address"
@@ -73,7 +86,7 @@ def gen_basic_node(
73
86
  logger.info(
74
87
  f"{depth_prefix}{LOGGER_PREFIX} Fetching parents {[x.address for x in all_parents]}"
75
88
  )
76
- parent_node: StrategyNode = source_concepts(
89
+ parent_node: StrategyNode | None = source_concepts(
77
90
  mandatory_list=all_parents,
78
91
  environment=environment,
79
92
  g=g,
@@ -92,14 +105,19 @@ def gen_basic_node(
92
105
  parent_node.add_output_concept(concept)
93
106
  for x in equivalent_optional:
94
107
  parent_node.add_output_concept(x)
95
-
96
- parent_node.remove_output_concepts(
97
- [
98
- x
99
- for x in parent_node.output_concepts
100
- if x.address not in [concept] + local_optional
101
- ]
108
+ targets = [concept] + local_optional
109
+ logger.info(
110
+ f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
102
111
  )
112
+ should_hide = [
113
+ x
114
+ for x in parent_node.output_concepts
115
+ if (
116
+ x.address not in targets
117
+ and not any(x.address in y.pseudonyms for y in targets)
118
+ )
119
+ ]
120
+ parent_node.hide_output_concepts(should_hide)
103
121
 
104
122
  logger.info(
105
123
  f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
@@ -143,7 +143,7 @@ def determine_induced_minimal_nodes(
143
143
  if not all([node in final.nodes for node in nodelist]):
144
144
  missing = [node for node in nodelist if node not in final.nodes]
145
145
  logger.debug(
146
- f"Skipping graph for {nodelist} as missing nodes {missing} from {final.nodes}"
146
+ f"Skipping graph for initial list {nodelist} as missing nodes {missing} from final graph {final.nodes}"
147
147
  )
148
148
  return None
149
149
  logger.debug(f"Found final graph {final.nodes}")
@@ -29,7 +29,7 @@ def gen_synonym_node(
29
29
  conditions: BuildWhereClause | None = None,
30
30
  accept_partial: bool = False,
31
31
  ) -> StrategyNode | None:
32
- local_prefix = f"[GEN_SYNONYM_NODE] {padding(depth)}"
32
+ local_prefix = f"{padding(depth)}[GEN_SYNONYM_NODE]"
33
33
  base_fingerprint = tuple([x.address for x in all_concepts])
34
34
  synonyms = defaultdict(list)
35
35
  synonym_count = 0
@@ -64,5 +64,6 @@ def gen_synonym_node(
64
64
  )
65
65
  if attempt:
66
66
  logger.info(f"{local_prefix} found inputs with {combo}")
67
+ print(attempt.output_concepts)
67
68
  return attempt
68
69
  return None
@@ -20,16 +20,22 @@ def gen_unnest_node(
20
20
  conditions: BuildWhereClause | None = None,
21
21
  ) -> StrategyNode | None:
22
22
  arguments = []
23
+ depth_prefix = "\t" * depth
23
24
  if isinstance(concept.lineage, BuildFunction):
24
25
  arguments = concept.lineage.concept_arguments
25
26
 
26
27
  equivalent_optional = [x for x in local_optional if x.lineage == concept.lineage]
28
+
27
29
  non_equivalent_optional = [
28
30
  x for x in local_optional if x not in equivalent_optional
29
31
  ]
32
+ all_parents = arguments + non_equivalent_optional
33
+ logger.info(
34
+ f"{depth_prefix}{LOGGER_PREFIX} unnest node for {concept} with lineage {concept.lineage} has parents {all_parents} and equivalent optional {equivalent_optional}"
35
+ )
30
36
  if arguments or local_optional:
31
37
  parent = source_concepts(
32
- mandatory_list=arguments + non_equivalent_optional,
38
+ mandatory_list=all_parents,
33
39
  environment=environment,
34
40
  g=g,
35
41
  depth=depth + 1,
@@ -311,19 +311,6 @@ class StrategyNode:
311
311
  self.rebuild_cache()
312
312
  return self
313
313
 
314
- def remove_output_concepts(
315
- self, concepts: List[BuildConcept], rebuild: bool = True
316
- ):
317
- for x in concepts:
318
- self.hidden_concepts.add(x.address)
319
- addresses = [x.address for x in concepts]
320
- self.output_concepts = [
321
- x for x in self.output_concepts if x.address not in addresses
322
- ]
323
- if rebuild:
324
- self.rebuild_cache()
325
- return self
326
-
327
314
  @property
328
315
  def usable_outputs(self) -> list[BuildConcept]:
329
316
  return [
@@ -105,7 +105,7 @@ class GroupNode(StrategyNode):
105
105
  if comp_grain.issubset(target_grain):
106
106
 
107
107
  logger.info(
108
- f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, is subset, no grain required"
108
+ f"{padding}{LOGGER_PREFIX} Group requirement check: {comp_grain}, {target_grain}, grain is subset of target, no group node required"
109
109
  )
110
110
  return GroupRequiredResponse(target_grain, comp_grain, False)
111
111
  # find out what extra is in the comp grain vs target grain
@@ -409,7 +409,7 @@ def get_node_joins(
409
409
 
410
410
 
411
411
  def get_disconnected_components(
412
- concept_map: Dict[str, Set[BuildConcept]]
412
+ concept_map: Dict[str, Set[BuildConcept]],
413
413
  ) -> Tuple[int, List]:
414
414
  """Find if any of the datasources are not linked"""
415
415
  import networkx as nx
@@ -608,8 +608,24 @@ def sort_select_output_processed(
608
608
  mapping = {x.address: x for x in cte.output_columns}
609
609
 
610
610
  new_output: list[BuildConcept] = []
611
- for x in output_addresses:
612
- new_output.append(mapping[x])
611
+ for x in query.output_columns:
612
+ if x.address in mapping:
613
+ new_output.append(mapping[x.address])
614
+ for oc in cte.output_columns:
615
+ if x.address in oc.pseudonyms:
616
+ # create a wrapper BuildConcept to render the pseudonym under the original name
617
+ new_output.append(
618
+ BuildConcept(
619
+ name=x.name,
620
+ namespace=x.namespace,
621
+ pseudonyms={oc.address},
622
+ datatype=oc.datatype,
623
+ purpose=oc.purpose,
624
+ grain=oc.grain,
625
+ build_is_aggregate=oc.build_is_aggregate,
626
+ )
627
+ )
628
+ break
613
629
 
614
630
  for oc in cte.output_columns:
615
631
  # add hidden back
@@ -637,17 +653,28 @@ def sort_select_output(
637
653
  if isinstance(query, ProcessedQuery):
638
654
  return sort_select_output_processed(cte, query)
639
655
 
640
- output_addresses = [
641
- c.address
642
- for c in query.output_components
643
- # if c.address not in query.hidden_components
644
- ]
645
-
646
656
  mapping = {x.address: x for x in cte.output_columns}
647
657
 
648
658
  new_output: list[BuildConcept] = []
649
- for x in output_addresses:
650
- new_output.append(mapping[x])
659
+ for x in query.output_components:
660
+ if x.address in mapping:
661
+ new_output.append(mapping[x.address])
662
+ else:
663
+ for oc in cte.output_columns:
664
+ if x.address in oc.pseudonyms:
665
+ # create a wrapper BuildConcept to render the pseudonym under the original name
666
+ new_output.append(
667
+ BuildConcept(
668
+ name=x.name,
669
+ namespace=x.namespace,
670
+ pseudonyms={oc.address},
671
+ datatype=oc.datatype,
672
+ purpose=oc.purpose,
673
+ grain=oc.grain,
674
+ build_is_aggregate=oc.build_is_aggregate,
675
+ )
676
+ )
677
+ break
651
678
  cte.output_columns = new_output
652
679
  cte.hidden_concepts = set(
653
680
  [
@@ -432,7 +432,7 @@ def get_query_node(
432
432
  )
433
433
  ds = SelectNode(
434
434
  output_concepts=build_statement.output_components,
435
- input_concepts=ds.output_concepts,
435
+ input_concepts=ds.usable_outputs,
436
436
  parents=[ds],
437
437
  environment=ds.environment,
438
438
  partial_concepts=ds.partial_concepts,
@@ -553,11 +553,11 @@ def process_query(
553
553
  root_cte.hidden_concepts = statement.hidden_components
554
554
 
555
555
  final_ctes = optimize_ctes(deduped_ctes, root_cte, statement)
556
- mapping = {x.address: x for x in cte.output_columns}
556
+
557
557
  return ProcessedQuery(
558
558
  order_by=root_cte.order_by,
559
559
  limit=statement.limit,
560
- output_columns=[mapping[x.address] for x in statement.output_components],
560
+ output_columns=statement.output_components,
561
561
  ctes=final_ctes,
562
562
  base=root_cte,
563
563
  hidden_columns=set([x for x in statement.hidden_components]),
@@ -5,7 +5,7 @@ from typing import Annotated, List, Optional, Union
5
5
  from pydantic import BaseModel, Field, computed_field, field_validator
6
6
  from pydantic.functional_validators import PlainValidator
7
7
 
8
- from trilogy.constants import CONFIG
8
+ from trilogy.constants import CONFIG, DEFAULT_NAMESPACE
9
9
  from trilogy.core.enums import (
10
10
  ConceptSource,
11
11
  FunctionClass,
@@ -281,7 +281,11 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
281
281
  # if the concept is a locally derived concept, it cannot ever be partial
282
282
  # but if it's a concept pulled in from upstream and we have a where clause, it should be partial
283
283
  ColumnAssignment(
284
- alias=c.address.replace(".", "_"),
284
+ alias=(
285
+ c.address.replace(".", "_")
286
+ if c.namespace != DEFAULT_NAMESPACE
287
+ else c.name
288
+ ),
285
289
  concept=environment.concepts[c.address].reference,
286
290
  modifiers=modifiers if c.address not in self.locally_derived else [],
287
291
  )
@@ -3,6 +3,7 @@ from typing import Annotated, List, Optional, Union
3
3
  from pydantic import BaseModel, Field
4
4
  from pydantic.functional_validators import PlainValidator
5
5
 
6
+ from trilogy.core.models.author import ConceptRef
6
7
  from trilogy.core.models.build import (
7
8
  BuildConcept,
8
9
  BuildDatasource,
@@ -14,7 +15,7 @@ from trilogy.core.statements.common import CopyQueryMixin, PersistQueryMixin
14
15
 
15
16
 
16
17
  class ProcessedQuery(BaseModel):
17
- output_columns: List[BuildConcept]
18
+ output_columns: List[ConceptRef]
18
19
  ctes: List[CTE | UnionCTE]
19
20
  base: CTE | UnionCTE
20
21
  hidden_columns: set[str] = Field(default_factory=set)
@@ -38,5 +39,5 @@ class ProcessedRawSQLStatement(BaseModel):
38
39
 
39
40
 
40
41
  class ProcessedShowStatement(BaseModel):
41
- output_columns: List[BuildConcept]
42
+ output_columns: List[ConceptRef]
42
43
  output_values: List[Union[BuildConcept, BuildDatasource, ProcessedQuery]]
@@ -33,7 +33,6 @@ from trilogy.core.models.build import (
33
33
  BuildRowsetItem,
34
34
  BuildSubselectComparison,
35
35
  BuildWindowItem,
36
- Factory,
37
36
  )
38
37
  from trilogy.core.models.core import (
39
38
  DataType,
@@ -904,7 +903,6 @@ class BaseDialect:
904
903
  | ProcessedRawSQLStatement
905
904
  | ProcessedCopyStatement
906
905
  ] = []
907
- factory = Factory(environment=environment)
908
906
  for statement in statements:
909
907
  if isinstance(statement, PersistStatement):
910
908
  if hooks:
@@ -939,11 +937,9 @@ class BaseDialect:
939
937
  output.append(
940
938
  ProcessedShowStatement(
941
939
  output_columns=[
942
- factory.build(
943
- environment.concepts[
944
- DEFAULT_CONCEPTS["query_text"].address
945
- ]
946
- )
940
+ environment.concepts[
941
+ DEFAULT_CONCEPTS["query_text"].address
942
+ ].reference
947
943
  ],
948
944
  output_values=[
949
945
  process_query(
@@ -984,29 +980,6 @@ class BaseDialect:
984
980
  return ";\n".join([str(x) for x in query.output_values])
985
981
  elif isinstance(query, ProcessedRawSQLStatement):
986
982
  return query.text
987
- select_columns: Dict[str, str] = {}
988
- cte_output_map = {}
989
- selected = set()
990
- output_addresses = [
991
- c.address
992
- for c in query.output_columns
993
- if c.address not in query.hidden_columns
994
- ]
995
-
996
- for c in query.base.output_columns:
997
- if c.address not in selected:
998
- select_columns[c.address] = (
999
- f"{query.base.name}.{safe_quote(c.safe_address, self.QUOTE_CHARACTER)}"
1000
- )
1001
- cte_output_map[c.address] = query.base
1002
- if c.address not in query.hidden_columns:
1003
- selected.add(c.address)
1004
- if not all([x in selected for x in output_addresses]):
1005
- missing = [x for x in output_addresses if x not in selected]
1006
- raise ValueError(
1007
- f"Did not get all output addresses in select - missing: {missing}, have"
1008
- f" {selected}"
1009
- )
1010
983
 
1011
984
  recursive = any(isinstance(x, RecursiveCTE) for x in query.ctes)
1012
985
 
@@ -8,8 +8,8 @@ from sqlalchemy.engine import CursorResult
8
8
 
9
9
  from trilogy.constants import Rendering, logger
10
10
  from trilogy.core.enums import FunctionType, Granularity, IOType
11
- from trilogy.core.models.author import Concept, Function
12
- from trilogy.core.models.build import BuildConcept, BuildFunction
11
+ from trilogy.core.models.author import Concept, ConceptRef, Function
12
+ from trilogy.core.models.build import BuildFunction
13
13
  from trilogy.core.models.core import ListWrapper, MapWrapper
14
14
  from trilogy.core.models.datasource import Datasource
15
15
  from trilogy.core.models.environment import Environment
@@ -61,7 +61,7 @@ class MockResult:
61
61
 
62
62
 
63
63
  def generate_result_set(
64
- columns: List[BuildConcept], output_data: list[Any]
64
+ columns: List[ConceptRef], output_data: list[Any]
65
65
  ) -> MockResult:
66
66
  names = [x.address.replace(".", "_") for x in columns]
67
67
  return MockResult(
@@ -90,7 +90,16 @@ class Executor(object):
90
90
  if self.dialect == Dialects.DATAFRAME:
91
91
  self.engine.setup(self.environment, self.connection)
92
92
 
93
- def execute_statement(self, statement) -> Optional[CursorResult]:
93
+ def execute_statement(
94
+ self,
95
+ statement: (
96
+ ProcessedQuery
97
+ | ProcessedCopyStatement
98
+ | ProcessedRawSQLStatement
99
+ | ProcessedQueryPersist
100
+ | ProcessedShowStatement
101
+ ),
102
+ ) -> Optional[CursorResult]:
94
103
  if not isinstance(
95
104
  statement,
96
105
  (
@@ -667,8 +667,14 @@ class ParseToObjects(Transformer):
667
667
  environment=self.environment,
668
668
  metadata=metadata,
669
669
  )
670
-
671
- if purpose and purpose != Purpose.AUTO and concept.purpose != purpose:
670
+ # let constant purposes exist to support round-tripping
671
+ # as a build concept may end up with a constant based on constant inlining happening recursively
672
+ if (
673
+ purpose
674
+ and purpose != Purpose.AUTO
675
+ and concept.purpose != purpose
676
+ and purpose != Purpose.CONSTANT
677
+ ):
672
678
  raise SyntaxError(
673
679
  f'Concept {name} purpose {concept.purpose} does not match declared purpose {purpose}. Suggest defaulting to "auto"'
674
680
  )
@@ -379,7 +379,7 @@
379
379
 
380
380
  int_lit: /\-?[0-9]+/
381
381
 
382
- float_lit: /[0-9]*\.[0-9]+/
382
+ float_lit: /\-?[0-9]*\.[0-9]+/
383
383
 
384
384
  array_lit: "[" (literal ",")* literal ","? "]"()
385
385
 
File without changes
File without changes
File without changes
File without changes