pytrilogy 0.0.3.56__tar.gz → 0.0.3.60__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 (149) hide show
  1. {pytrilogy-0.0.3.56/pytrilogy.egg-info → pytrilogy-0.0.3.60}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_discovery_nodes.py +10 -2
  5. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_functions.py +17 -0
  6. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_user_functions.py +2 -0
  7. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/authoring/__init__.py +12 -1
  9. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/constants.py +1 -0
  10. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/enums.py +1 -0
  11. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/execute.py +4 -1
  12. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/optimization.py +4 -4
  13. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/concept_strategies_v3.py +4 -3
  14. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/discovery_node_factory.py +9 -3
  15. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/basic_node.py +29 -11
  16. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/node_merge_node.py +16 -8
  17. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/synonym_node.py +2 -1
  18. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/unnest_node.py +7 -1
  19. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/base_node.py +0 -13
  20. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/group_node.py +1 -1
  21. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/utility.py +38 -11
  22. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/query_processor.py +3 -3
  23. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/statements/author.py +6 -2
  24. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/statements/execute.py +3 -2
  25. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/base.py +21 -30
  26. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/bigquery.py +2 -2
  27. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/common.py +10 -4
  28. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/executor.py +13 -4
  29. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/common.py +7 -2
  30. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/parse_engine.py +8 -2
  31. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/trilogy.lark +1 -1
  32. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/std/date.preql +4 -1
  33. pytrilogy-0.0.3.60/trilogy/std/ranking.preql +6 -0
  34. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/LICENSE.md +0 -0
  35. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/README.md +0 -0
  36. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/pyproject.toml +0 -0
  37. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/pytrilogy.egg-info/dependency_links.txt +0 -0
  38. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/pytrilogy.egg-info/entry_points.txt +0 -0
  39. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/pytrilogy.egg-info/requires.txt +0 -0
  40. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/pytrilogy.egg-info/top_level.txt +0 -0
  41. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/setup.cfg +0 -0
  42. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/setup.py +0 -0
  43. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_datatypes.py +0 -0
  44. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_declarations.py +0 -0
  45. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_derived_concepts.py +0 -0
  46. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_enums.py +0 -0
  47. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_environment.py +0 -0
  48. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_executor.py +0 -0
  49. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_failure.py +0 -0
  50. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_imports.py +0 -0
  51. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_metadata.py +0 -0
  52. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_models.py +0 -0
  53. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_multi_join_assignments.py +0 -0
  54. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_parse_engine.py +0 -0
  55. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_parsing.py +0 -0
  56. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_parsing_failures.py +0 -0
  57. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_partial_handling.py +0 -0
  58. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_query_processing.py +0 -0
  59. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_query_render.py +0 -0
  60. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_select.py +0 -0
  61. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_show.py +0 -0
  62. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_statements.py +0 -0
  63. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_typing.py +0 -0
  64. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_undefined_concept.py +0 -0
  65. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/tests/test_where_clause.py +0 -0
  66. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/compiler.py +0 -0
  67. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/constants.py +0 -0
  68. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/__init__.py +0 -0
  69. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/env_processor.py +0 -0
  70. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/environment_helpers.py +0 -0
  71. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/ergonomics.py +0 -0
  72. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/exceptions.py +0 -0
  73. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/functions.py +0 -0
  74. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/graph_models.py +0 -0
  75. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/internal.py +0 -0
  76. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/__init__.py +0 -0
  77. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/author.py +0 -0
  78. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/build.py +0 -0
  79. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/build_environment.py +0 -0
  80. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/core.py +0 -0
  81. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/datasource.py +0 -0
  82. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/models/environment.py +0 -0
  83. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/optimizations/__init__.py +0 -0
  84. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/optimizations/base_optimization.py +0 -0
  85. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/optimizations/inline_datasource.py +0 -0
  86. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  87. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/__init__.py +0 -0
  88. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/discovery_loop.py +0 -0
  89. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/discovery_utility.py +0 -0
  90. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/discovery_validation.py +0 -0
  91. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/graph_utils.py +0 -0
  92. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/__init__.py +0 -0
  93. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/common.py +0 -0
  94. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  95. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/group_node.py +0 -0
  96. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  97. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  98. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  99. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  100. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  101. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  102. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  103. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/select_node.py +0 -0
  104. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/union_node.py +0 -0
  105. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/node_generators/window_node.py +0 -0
  106. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/__init__.py +0 -0
  107. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/filter_node.py +0 -0
  108. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/merge_node.py +0 -0
  109. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  110. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  111. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/union_node.py +0 -0
  112. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  113. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/processing/nodes/window_node.py +0 -0
  114. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/statements/__init__.py +0 -0
  115. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/statements/build.py +0 -0
  116. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/statements/common.py +0 -0
  117. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/core/utility.py +0 -0
  118. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/__init__.py +0 -0
  119. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/config.py +0 -0
  120. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/dataframe.py +0 -0
  121. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/duckdb.py +0 -0
  122. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/enums.py +0 -0
  123. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/postgres.py +0 -0
  124. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/presto.py +0 -0
  125. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/snowflake.py +0 -0
  126. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/dialect/sql_server.py +0 -0
  127. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/engine.py +0 -0
  128. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/hooks/__init__.py +0 -0
  129. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/hooks/base_hook.py +0 -0
  130. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/hooks/graph_hook.py +0 -0
  131. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/hooks/query_debugger.py +0 -0
  132. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/metadata/__init__.py +0 -0
  133. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parser.py +0 -0
  134. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/__init__.py +0 -0
  135. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/config.py +0 -0
  136. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/exceptions.py +0 -0
  137. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/helpers.py +0 -0
  138. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/parsing/render.py +0 -0
  139. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/py.typed +0 -0
  140. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/render.py +0 -0
  141. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/scripts/__init__.py +0 -0
  142. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/scripts/trilogy.py +0 -0
  143. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/std/__init__.py +0 -0
  144. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/std/display.preql +0 -0
  145. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/std/geography.preql +0 -0
  146. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/std/money.preql +0 -0
  147. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/std/net.preql +0 -0
  148. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/trilogy/std/report.preql +0 -0
  149. {pytrilogy-0.0.3.56 → pytrilogy-0.0.3.60}/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.60
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.60
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -143,4 +143,5 @@ trilogy/std/display.preql
143
143
  trilogy/std/geography.preql
144
144
  trilogy/std/money.preql
145
145
  trilogy/std/net.preql
146
+ trilogy/std/ranking.preql
146
147
  trilogy/std/report.preql
@@ -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.60"
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
  ]
@@ -2,3 +2,4 @@ CONSTANT_DATASET: str = "preql_internal_constant_dataset"
2
2
  ALL_ROWS_CONCEPT = "all_rows"
3
3
  INTERNAL_NAMESPACE = "__preql_internal"
4
4
  PERSISTED_CONCEPT_PREFIX = "__pre_persist"
5
+ UNNEST_NAME = "_unnest_alias"
@@ -7,6 +7,7 @@ class UnnestMode(Enum):
7
7
  DIRECT = "direct"
8
8
  CROSS_APPLY = "cross_apply"
9
9
  CROSS_JOIN = "cross_join"
10
+ CROSS_JOIN_UNNEST = "cross_join_unnest"
10
11
  CROSS_JOIN_ALIAS = "cross_join_alias"
11
12
  SNOWFLAKE = "snowflake"
12
13
 
@@ -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]
@@ -367,7 +373,7 @@ class RootNodeHandler:
367
373
  ) -> Optional[StrategyNode]:
368
374
  logger.info(
369
375
  f"{depth_to_prefix(self.ctx.depth)}{LOGGER_PREFIX} "
370
- f"Could not resolve root concepts, checking for synonyms"
376
+ f"Could not resolve root concepts, checking for synonyms for {root_targets}"
371
377
  )
372
378
 
373
379
  if not self.ctx.history.check_started(
@@ -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]}"
@@ -88,7 +88,10 @@ def determine_induced_minimal_nodes(
88
88
  for node in G.nodes:
89
89
  if concepts.get(node):
90
90
  lookup: BuildConcept = concepts[node]
91
- if lookup.derivation in (Derivation.CONSTANT,):
91
+ # inclusion of aggregates can create ambiguous node relation chains
92
+ # there may be a better way to handle this
93
+ # can be revisited if we need to connect a derived synonym based on an aggregate
94
+ if lookup.derivation in (Derivation.CONSTANT, Derivation.AGGREGATE):
92
95
  nodes_to_remove.append(node)
93
96
  # purge a node if we're already looking for all it's parents
94
97
  if filter_downstream and lookup.derivation not in (Derivation.ROOT,):
@@ -112,6 +115,7 @@ def determine_induced_minimal_nodes(
112
115
  return None
113
116
  H.remove_nodes_from(list(x for x in H.nodes if x not in paths))
114
117
  sG: nx.Graph = ax.steinertree.steiner_tree(H, nodelist).copy()
118
+ logger.debug("Steiner tree found for nodes %s", nodelist)
115
119
  final: nx.DiGraph = nx.subgraph(G, sG.nodes).copy()
116
120
 
117
121
  for edge in G.edges:
@@ -143,7 +147,7 @@ def determine_induced_minimal_nodes(
143
147
  if not all([node in final.nodes for node in nodelist]):
144
148
  missing = [node for node in nodelist if node not in final.nodes]
145
149
  logger.debug(
146
- f"Skipping graph for {nodelist} as missing nodes {missing} from {final.nodes}"
150
+ f"Skipping graph for initial list {nodelist} as missing nodes {missing} from final graph {final.nodes}"
147
151
  )
148
152
  return None
149
153
  logger.debug(f"Found final graph {final.nodes}")
@@ -228,11 +232,14 @@ def resolve_weak_components(
228
232
  # to ensure there are not ambiguous discovery paths
229
233
  # (if we did not care about raising ambiguity errors, we could just use the first one)
230
234
  count = 0
231
- node_list = [
232
- concept_to_node(c.with_default_grain())
233
- for c in all_concepts
234
- if "__preql_internal" not in c.address
235
- ]
235
+ node_list = sorted(
236
+ [
237
+ concept_to_node(c.with_default_grain())
238
+ for c in all_concepts
239
+ if "__preql_internal" not in c.address
240
+ ]
241
+ )
242
+ logger.debug(f"Resolving weak components for {node_list} in {search_graph.nodes}")
236
243
  synonyms: set[str] = set()
237
244
  for x in all_concepts:
238
245
  synonyms = synonyms.union(x.pseudonyms)
@@ -354,7 +361,7 @@ def subgraphs_to_merge_node(
354
361
  parents.append(parent)
355
362
  input_c = []
356
363
  for x in parents:
357
- for y in x.output_concepts:
364
+ for y in x.usable_outputs:
358
365
  input_c.append(y)
359
366
  if len(parents) == 1 and enable_early_exit:
360
367
  logger.info(
@@ -392,6 +399,7 @@ def gen_merge_node(
392
399
  )
393
400
  else:
394
401
  all_search_concepts = all_concepts
402
+ all_search_concepts = sorted(all_search_concepts, key=lambda x: x.address)
395
403
  for filter_downstream in [True, False]:
396
404
  weak_resolve = resolve_weak_components(
397
405
  all_search_concepts,
@@ -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]]