pytrilogy 0.0.3.45__tar.gz → 0.0.3.47__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 (142) hide show
  1. {pytrilogy-0.0.3.45/pytrilogy.egg-info → pytrilogy-0.0.3.47}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_functions.py +3 -0
  4. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_parsing_failures.py +2 -2
  5. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_partial_handling.py +3 -3
  6. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/__init__.py +1 -1
  7. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/environment_helpers.py +2 -27
  8. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/author.py +4 -1
  9. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/build.py +33 -29
  10. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/filter_node.py +31 -16
  11. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/__init__.py +2 -0
  12. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/query_processor.py +1 -2
  13. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/author.py +21 -9
  14. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/base.py +1 -0
  15. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/common.py +74 -16
  16. pytrilogy-0.0.3.47/trilogy/parsing/exceptions.py +8 -0
  17. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/parse_engine.py +33 -28
  18. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/trilogy.lark +1 -4
  19. pytrilogy-0.0.3.45/trilogy/parsing/exceptions.py +0 -2
  20. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/LICENSE.md +0 -0
  21. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/README.md +0 -0
  22. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pyproject.toml +0 -0
  23. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/SOURCES.txt +0 -0
  24. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/dependency_links.txt +0 -0
  25. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/entry_points.txt +0 -0
  26. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/requires.txt +0 -0
  27. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/pytrilogy.egg-info/top_level.txt +0 -0
  28. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/setup.cfg +0 -0
  29. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/setup.py +0 -0
  30. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_datatypes.py +0 -0
  31. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_declarations.py +0 -0
  32. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_derived_concepts.py +0 -0
  33. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_discovery_nodes.py +0 -0
  34. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_enums.py +0 -0
  35. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_environment.py +0 -0
  36. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_executor.py +0 -0
  37. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_failure.py +0 -0
  38. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_imports.py +0 -0
  39. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_metadata.py +0 -0
  40. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_models.py +0 -0
  41. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_multi_join_assignments.py +0 -0
  42. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_parse_engine.py +0 -0
  43. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_parsing.py +0 -0
  44. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_query_processing.py +0 -0
  45. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_query_render.py +0 -0
  46. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_select.py +0 -0
  47. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_show.py +0 -0
  48. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_statements.py +0 -0
  49. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_typing.py +0 -0
  50. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_undefined_concept.py +0 -0
  51. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_user_functions.py +0 -0
  52. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/tests/test_where_clause.py +0 -0
  53. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/authoring/__init__.py +0 -0
  54. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/compiler.py +0 -0
  55. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/constants.py +0 -0
  56. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/__init__.py +0 -0
  57. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/constants.py +0 -0
  58. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/enums.py +0 -0
  59. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/env_processor.py +0 -0
  60. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/ergonomics.py +0 -0
  61. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/exceptions.py +0 -0
  62. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/functions.py +0 -0
  63. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/graph_models.py +0 -0
  64. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/internal.py +0 -0
  65. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/__init__.py +0 -0
  66. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/build_environment.py +0 -0
  67. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/core.py +0 -0
  68. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/datasource.py +0 -0
  69. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/environment.py +0 -0
  70. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/models/execute.py +0 -0
  71. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimization.py +0 -0
  72. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/__init__.py +0 -0
  73. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/base_optimization.py +0 -0
  74. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/inline_constant.py +0 -0
  75. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/inline_datasource.py +0 -0
  76. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  77. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/__init__.py +0 -0
  78. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  79. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/graph_utils.py +0 -0
  80. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/__init__.py +0 -0
  81. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  82. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/common.py +0 -0
  83. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/group_node.py +0 -0
  84. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  85. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  86. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  87. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  88. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  89. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  90. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  91. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/select_node.py +0 -0
  92. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  93. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/union_node.py +0 -0
  94. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  95. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/node_generators/window_node.py +0 -0
  96. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/base_node.py +0 -0
  97. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/filter_node.py +0 -0
  98. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/group_node.py +0 -0
  99. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/merge_node.py +0 -0
  100. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  101. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/union_node.py +0 -0
  102. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  103. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/nodes/window_node.py +0 -0
  104. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/processing/utility.py +0 -0
  105. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/__init__.py +0 -0
  106. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/build.py +0 -0
  107. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/common.py +0 -0
  108. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/core/statements/execute.py +0 -0
  109. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/__init__.py +0 -0
  110. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/bigquery.py +0 -0
  111. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/common.py +0 -0
  112. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/config.py +0 -0
  113. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/dataframe.py +0 -0
  114. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/duckdb.py +0 -0
  115. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/enums.py +0 -0
  116. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/postgres.py +0 -0
  117. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/presto.py +0 -0
  118. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/snowflake.py +0 -0
  119. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/dialect/sql_server.py +0 -0
  120. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/engine.py +0 -0
  121. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/executor.py +0 -0
  122. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/__init__.py +0 -0
  123. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/base_hook.py +0 -0
  124. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/graph_hook.py +0 -0
  125. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/hooks/query_debugger.py +0 -0
  126. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/metadata/__init__.py +0 -0
  127. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parser.py +0 -0
  128. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/__init__.py +0 -0
  129. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/config.py +0 -0
  130. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/helpers.py +0 -0
  131. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/parsing/render.py +0 -0
  132. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/py.typed +0 -0
  133. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/render.py +0 -0
  134. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/scripts/__init__.py +0 -0
  135. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/scripts/trilogy.py +0 -0
  136. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/__init__.py +0 -0
  137. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/date.preql +0 -0
  138. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/display.preql +0 -0
  139. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/geography.preql +0 -0
  140. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/money.preql +0 -0
  141. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/std/report.preql +0 -0
  142. {pytrilogy-0.0.3.45 → pytrilogy-0.0.3.47}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.45
3
+ Version: 0.0.3.47
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.45
3
+ Version: 0.0.3.47
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -123,6 +123,7 @@ def test_date_functions(test_environment):
123
123
  year(order_timestamp) -> order_year,
124
124
  date_trunc(order_timestamp, month) -> order_month_trunc,
125
125
  date_add(order_timestamp, month, 1) -> one_month_post_order,
126
+ date_sub(order_timestamp, month, 1) -> one_month_pre_order,
126
127
  date_trunc(order_timestamp, day) -> order_day_trunc,
127
128
  date_trunc(order_timestamp, year) -> order_year_trunc,
128
129
  date_trunc(order_timestamp, hour) -> order_hour_trunc,
@@ -212,6 +213,7 @@ def test_math_functions(test_environment):
212
213
  property order_sub <- revenue - 2;
213
214
  property order_id.order_nested <- revenue * 2/2;
214
215
  property order_id.rounded <- round(revenue + 2.01,2);
216
+ property order_id.rounded_default <- round(revenue + 2.01);
215
217
  constant random <- random(1);
216
218
  select
217
219
  order_id,
@@ -221,6 +223,7 @@ def test_math_functions(test_environment):
221
223
  order_sub,
222
224
  order_add,
223
225
  rounded,
226
+ rounded_default,
224
227
  random,
225
228
  ;"""
226
229
  env, parsed = parse(declarations, environment=test_environment)
@@ -8,7 +8,7 @@ from trilogy.core.models.environment import (
8
8
  EnvironmentOptions,
9
9
  )
10
10
  from trilogy.parsing.parse_engine import (
11
- ParseError,
11
+ NameShadowError,
12
12
  parse_text,
13
13
  )
14
14
 
@@ -77,7 +77,7 @@ SELECT
77
77
  1+2->scalar
78
78
  ;
79
79
  """
80
- with raises(ParseError) as e:
80
+ with raises(NameShadowError) as e:
81
81
  env, parsed = parse_text(
82
82
  x, parse_config=Parsing(strict_name_shadow_enforcement=True)
83
83
  )
@@ -12,7 +12,7 @@ from trilogy.core.processing.concept_strategies_v3 import History, search_concep
12
12
  from trilogy.core.processing.node_generators import (
13
13
  gen_filter_node,
14
14
  )
15
- from trilogy.core.processing.nodes import MergeNode
15
+ from trilogy.core.processing.nodes import FilterNode
16
16
  from trilogy.core.query_processor import generate_graph
17
17
  from trilogy.hooks.query_debugger import DebuggingHook
18
18
 
@@ -139,8 +139,8 @@ def test_partial_assignment():
139
139
  g=g,
140
140
  depth=0,
141
141
  )
142
- assert isinstance(sourced, MergeNode)
143
- assert len(sourced.parents) == 2
142
+ assert isinstance(sourced, FilterNode)
143
+ assert len(sourced.parents) == 1
144
144
 
145
145
 
146
146
  def test_filter_query():
@@ -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.45"
7
+ __version__ = "0.0.3.47"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -6,23 +6,8 @@ from trilogy.core.models.core import DataType, StructType, arg_to_datatype
6
6
  from trilogy.core.models.environment import Environment
7
7
  from trilogy.parsing.common import Meta
8
8
 
9
- FUNCTION_DESCRIPTION_MAPS = {
10
- FunctionType.DATE: "The date part of a timestamp/date. Integer, 0-31 depending on month.",
11
- FunctionType.MONTH: "The month part of a timestamp/date. Integer, 1-12.",
12
- FunctionType.YEAR: "The year part of a timestamp/date. Integer.",
13
- FunctionType.QUARTER: "The quarter part of a timestamp/date. Integer, 1-4.",
14
- FunctionType.DAY_OF_WEEK: "The day of the week part of a timestamp/date. Integer, 0-6.",
15
- FunctionType.HOUR: "The hour part of a timestamp. Integer, 0-23.",
16
- FunctionType.MINUTE: "The minute part of a timestamp. Integer, 0-59.",
17
- FunctionType.SECOND: "The second part of a timestamp. Integer, 0-59.",
18
- }
19
-
20
9
 
21
10
  def generate_date_concepts(concept: Concept, environment: Environment):
22
- if concept.metadata and concept.metadata.description:
23
- base_description = concept.metadata.description
24
- else:
25
- base_description = f"a {concept.address}"
26
11
  if concept.metadata and concept.metadata.line_number:
27
12
  base_line_number = concept.metadata.line_number
28
13
  else:
@@ -67,7 +52,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
67
52
  [concept.address],
68
53
  ),
69
54
  metadata=Metadata(
70
- description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
71
55
  line_number=base_line_number,
72
56
  concept_source=ConceptSource.AUTO_DERIVED,
73
57
  ),
@@ -95,7 +79,7 @@ def generate_date_concepts(concept: Concept, environment: Environment):
95
79
  [concept.address],
96
80
  ),
97
81
  metadata=Metadata(
98
- description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
82
+ # description=f"Auto-derived from {base_description}. The date truncated to the {grain.value}.",
99
83
  line_number=base_line_number,
100
84
  concept_source=ConceptSource.AUTO_DERIVED,
101
85
  ),
@@ -105,10 +89,6 @@ def generate_date_concepts(concept: Concept, environment: Environment):
105
89
 
106
90
 
107
91
  def generate_datetime_concepts(concept: Concept, environment: Environment):
108
- if concept.metadata and concept.metadata.description:
109
- base_description = concept.metadata.description
110
- else:
111
- base_description = concept.address
112
92
  if concept.metadata and concept.metadata.line_number:
113
93
  base_line_number = concept.metadata.line_number
114
94
  else:
@@ -146,7 +126,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
146
126
  [concept.address],
147
127
  ),
148
128
  metadata=Metadata(
149
- description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}",
150
129
  line_number=base_line_number,
151
130
  concept_source=ConceptSource.AUTO_DERIVED,
152
131
  ),
@@ -157,10 +136,6 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
157
136
 
158
137
 
159
138
  def generate_key_concepts(concept: Concept, environment: Environment):
160
- if concept.metadata and concept.metadata.description:
161
- base_description = concept.metadata.description
162
- else:
163
- base_description = f"a {concept.datatype.value}"
164
139
  if concept.metadata and concept.metadata.line_number:
165
140
  base_line_number = concept.metadata.line_number
166
141
  else:
@@ -186,7 +161,7 @@ def generate_key_concepts(concept: Concept, environment: Environment):
186
161
  namespace=concept.namespace,
187
162
  keys=set(),
188
163
  metadata=Metadata(
189
- description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
164
+ # description=f"Auto-derived integer. The {ftype.value} of {concept.address}, {base_description}",
190
165
  line_number=base_line_number,
191
166
  concept_source=ConceptSource.AUTO_DERIVED,
192
167
  ),
@@ -445,13 +445,16 @@ class Grain(Namespaced, BaseModel):
445
445
  concepts: Iterable[Concept | ConceptRef | str],
446
446
  environment: Environment | None = None,
447
447
  where_clause: WhereClause | None = None,
448
+ local_concepts: dict[str, Concept] | None = None,
448
449
  ) -> Grain:
449
450
  from trilogy.parsing.common import concepts_to_grain_concepts
450
451
 
451
452
  x = Grain.model_construct(
452
453
  components={
453
454
  c.address
454
- for c in concepts_to_grain_concepts(concepts, environment=environment)
455
+ for c in concepts_to_grain_concepts(
456
+ concepts, environment=environment, local_concepts=local_concepts
457
+ )
455
458
  },
456
459
  where_clause=where_clause,
457
460
  )
@@ -126,9 +126,14 @@ def concept_is_relevant(
126
126
 
127
127
  return False
128
128
  if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
129
- if any([c in others for c in concept.keys]):
130
-
129
+ if all([c in others for c in concept.keys]):
131
130
  return False
131
+ if (
132
+ concept.purpose == Purpose.KEY
133
+ and concept.keys
134
+ and all([c in others for c in concept.keys])
135
+ ):
136
+ return False
132
137
  if concept.purpose in (Purpose.METRIC,):
133
138
  if all([c in others for c in concept.grain.components]):
134
139
  return False
@@ -1458,11 +1463,35 @@ class Factory:
1458
1463
  {} if local_concepts is None else local_concepts
1459
1464
  )
1460
1465
 
1466
+ def instantiate_concept(
1467
+ self,
1468
+ arg: (
1469
+ AggregateWrapper
1470
+ | FunctionCallWrapper
1471
+ | WindowItem
1472
+ | FilterItem
1473
+ | Function
1474
+ | ListWrapper[Any]
1475
+ | MapWrapper[Any, Any]
1476
+ | int
1477
+ | float
1478
+ | str
1479
+ ),
1480
+ ) -> tuple[Concept, BuildConcept]:
1481
+ from trilogy.parsing.common import arbitrary_to_concept
1482
+
1483
+ new = arbitrary_to_concept(
1484
+ arg,
1485
+ environment=self.environment,
1486
+ )
1487
+ built = self.build(new)
1488
+ self.local_concepts[new.address] = built
1489
+ return new, built
1490
+
1461
1491
  @singledispatchmethod
1462
1492
  def build(self, base):
1463
1493
  raise NotImplementedError("Cannot build {}".format(type(base)))
1464
1494
 
1465
- @build.register
1466
1495
  @build.register
1467
1496
  def _(
1468
1497
  self,
@@ -1496,31 +1525,6 @@ class Factory:
1496
1525
  ):
1497
1526
  return base
1498
1527
 
1499
- def instantiate_concept(
1500
- self,
1501
- arg: (
1502
- AggregateWrapper
1503
- | FunctionCallWrapper
1504
- | WindowItem
1505
- | FilterItem
1506
- | Function
1507
- | ListWrapper[Any]
1508
- | MapWrapper[Any, Any]
1509
- | int
1510
- | float
1511
- | str
1512
- ),
1513
- ) -> tuple[Concept, BuildConcept]:
1514
- from trilogy.parsing.common import arbitrary_to_concept
1515
-
1516
- new = arbitrary_to_concept(
1517
- arg,
1518
- environment=self.environment,
1519
- )
1520
- built = self.build(new)
1521
- self.local_concepts[new.address] = built
1522
- return new, built
1523
-
1524
1528
  @build.register
1525
1529
  def _(self, base: None) -> None:
1526
1530
  return base
@@ -1626,6 +1630,7 @@ class Factory:
1626
1630
  derivation, final_grain, build_lineage
1627
1631
  )
1628
1632
  is_aggregate = Concept.calculate_is_aggregate(build_lineage)
1633
+
1629
1634
  rval = BuildConcept.model_construct(
1630
1635
  name=base.name,
1631
1636
  datatype=base.datatype,
@@ -1646,7 +1651,6 @@ class Factory:
1646
1651
 
1647
1652
  @build.register
1648
1653
  def _(self, base: AggregateWrapper) -> BuildAggregateWrapper:
1649
-
1650
1654
  if not base.by:
1651
1655
  by = [
1652
1656
  self.build(self.environment.concepts[c]) for c in self.grain.components
@@ -56,12 +56,40 @@ def gen_filter_node(
56
56
  continue
57
57
  if conditions and conditions == where:
58
58
  optional_included.append(x)
59
+
60
+ # sometimes, it's okay to include other local optional above the filter
61
+ # in case it is, prep our list
62
+ extra_row_level_optional: list[BuildConcept] = []
63
+ for x in local_optional:
64
+ if x.address in optional_included:
65
+ continue
66
+ extra_row_level_optional.append(x)
67
+
68
+ # this flag controls whether we materialize the filter as a where on the prior CTE
69
+ # or do the filtering inline as a case statement
70
+ optimized_pushdown = False
71
+ if not is_scalar_condition(where.conditional):
72
+ optimized_pushdown = False
73
+ elif not local_optional:
74
+ optimized_pushdown = True
75
+ elif conditions and conditions == where:
76
+ logger.info(
77
+ f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
78
+ )
79
+ optimized_pushdown = True
80
+ elif optional_included == local_optional:
81
+ logger.info(
82
+ f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
83
+ )
84
+ optimized_pushdown = True
59
85
  logger.info(
60
86
  f"{padding(depth)}{LOGGER_PREFIX} filter `{concept}` condition `{concept.lineage.where}` derived from {immediate_parent.address} row parents {[x.address for x in parent_row_concepts]} and {[[y.address] for x in parent_existence_concepts for y in x]} existence parents"
61
87
  )
62
88
  # we'll populate this with the row parent
63
89
  # and the existence parent(s)
64
90
  core_parents = []
91
+ if not optimized_pushdown:
92
+ parent_row_concepts += extra_row_level_optional
65
93
 
66
94
  row_parent: StrategyNode = source_concepts(
67
95
  mandatory_list=parent_row_concepts,
@@ -99,21 +127,6 @@ def gen_filter_node(
99
127
  )
100
128
  return None
101
129
 
102
- optimized_pushdown = False
103
- if not is_scalar_condition(where.conditional):
104
- optimized_pushdown = False
105
- elif not local_optional:
106
- optimized_pushdown = True
107
- elif conditions and conditions == where:
108
- logger.info(
109
- f"{padding(depth)}{LOGGER_PREFIX} query conditions are the same as filter conditions, can optimize across all concepts"
110
- )
111
- optimized_pushdown = True
112
- elif optional_included == local_optional:
113
- logger.info(
114
- f"{padding(depth)}{LOGGER_PREFIX} all optional concepts are included in the filter, can optimize across all concepts"
115
- )
116
- optimized_pushdown = True
117
130
  if optimized_pushdown:
118
131
  logger.info(
119
132
  f"{padding(depth)}{LOGGER_PREFIX} returning optimized filter node with pushdown to parent with condition {where.conditional}"
@@ -193,7 +206,9 @@ def gen_filter_node(
193
206
  + optional_outputs
194
207
  )
195
208
  return filter_node
196
-
209
+ logger.info(
210
+ f"{padding(depth)}{LOGGER_PREFIX} need to enrich filter node with additional concepts {[x.address for x in local_optional if x.address not in filter_node.output_concepts]}"
211
+ )
197
212
  enrich_node: StrategyNode = source_concepts( # this fetches the parent + join keys
198
213
  # to then connect to the rest of the query
199
214
  mandatory_list=[immediate_parent] + parent_row_concepts + local_optional,
@@ -1,6 +1,7 @@
1
1
  from pydantic import BaseModel, ConfigDict, Field
2
2
 
3
3
  from trilogy.core.exceptions import UnresolvableQueryException
4
+ from trilogy.core.models.author import Concept
4
5
  from trilogy.core.models.build import BuildConcept, BuildWhereClause
5
6
  from trilogy.core.models.build_environment import BuildEnvironment
6
7
  from trilogy.core.models.environment import Environment
@@ -17,6 +18,7 @@ from .window_node import WindowNode
17
18
 
18
19
  class History(BaseModel):
19
20
  base_environment: Environment
21
+ local_base_concepts: dict[str, Concept] = Field(default_factory=dict)
20
22
  history: dict[str, StrategyNode | None] = Field(default_factory=dict)
21
23
  select_history: dict[str, StrategyNode | None] = Field(default_factory=dict)
22
24
  started: dict[str, int] = Field(default_factory=dict)
@@ -373,10 +373,9 @@ def get_query_node(
373
373
  ) -> StrategyNode:
374
374
  if not statement.output_components:
375
375
  raise ValueError(f"Statement has no output components {statement}")
376
-
377
376
  history = history or History(base_environment=environment)
378
377
  build_statement: BuildSelectLineage | BuildMultiSelectLineage = Factory(
379
- environment=environment
378
+ environment=environment,
380
379
  ).build(statement)
381
380
 
382
381
  # build_statement = statement
@@ -7,6 +7,7 @@ from pydantic.functional_validators import PlainValidator
7
7
 
8
8
  from trilogy.constants import CONFIG
9
9
  from trilogy.core.enums import (
10
+ ConceptSource,
10
11
  FunctionClass,
11
12
  IOType,
12
13
  Modifier,
@@ -134,7 +135,7 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
134
135
  meta=meta or Metadata(),
135
136
  )
136
137
 
137
- output.grain = output.calculate_grain(environment)
138
+ output.grain = output.calculate_grain(environment, output.local_concepts)
138
139
 
139
140
  for x in selection:
140
141
  if x.is_undefined and environment.concepts.fail_on_missing:
@@ -144,12 +145,13 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
144
145
  elif isinstance(x.content, ConceptTransform):
145
146
  if isinstance(x.content.output, UndefinedConcept):
146
147
  continue
147
- if (
148
- CONFIG.parsing.select_as_definition
149
- and not environment.frozen
150
- and x.concept.address not in environment.concepts
151
- ):
152
- environment.add_concept(x.content.output)
148
+ if CONFIG.parsing.select_as_definition and not environment.frozen:
149
+ if x.concept.address not in environment.concepts:
150
+ environment.add_concept(x.content.output)
151
+ elif x.concept.address in environment.concepts:
152
+ version = environment.concepts[x.concept.address]
153
+ if version.metadata.concept_source == ConceptSource.SELECT:
154
+ environment.add_concept(x.content.output, force=True)
153
155
  x.content.output = x.content.output.set_select_grain(
154
156
  output.grain, environment
155
157
  )
@@ -160,16 +162,26 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
160
162
  output.local_concepts[x.content.address] = environment.concepts[
161
163
  x.content.address
162
164
  ]
165
+
166
+ output.grain = output.calculate_grain(environment, output.local_concepts)
167
+
163
168
  output.validate_syntax(environment)
164
169
  return output
165
170
 
166
- def calculate_grain(self, environment: Environment | None = None) -> Grain:
171
+ def calculate_grain(
172
+ self,
173
+ environment: Environment | None = None,
174
+ local_concepts: dict[str, Concept] | None = None,
175
+ ) -> Grain:
167
176
  targets = []
168
177
  for x in self.selection:
169
178
  targets.append(x.concept)
170
179
 
171
180
  result = Grain.from_concepts(
172
- targets, where_clause=self.where_clause, environment=environment
181
+ targets,
182
+ where_clause=self.where_clause,
183
+ environment=environment,
184
+ local_concepts=local_concepts,
173
185
  )
174
186
  return result
175
187
 
@@ -199,6 +199,7 @@ FUNCTION_MAP = {
199
199
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
200
200
  FunctionType.DATE_PART: lambda x: f"date_part({x[0]},{x[1]})",
201
201
  FunctionType.DATE_ADD: lambda x: f"date_add({x[0]},{x[1]}, {x[2]})",
202
+ FunctionType.DATE_SUB: lambda x: f"date_sub({x[0]},{x[1]}, {x[2]})",
202
203
  FunctionType.DATE_DIFF: lambda x: f"date_diff({x[0]},{x[1]}, {x[2]})",
203
204
  FunctionType.DATE: lambda x: f"date({x[0]})",
204
205
  FunctionType.DATETIME: lambda x: f"datetime({x[0]})",
@@ -24,6 +24,7 @@ from trilogy.core.models.author import (
24
24
  AlignClause,
25
25
  AlignItem,
26
26
  Concept,
27
+ ConceptArgs,
27
28
  ConceptRef,
28
29
  FilterItem,
29
30
  Function,
@@ -191,16 +192,53 @@ def constant_to_concept(
191
192
  )
192
193
 
193
194
 
195
+ def atom_is_relevant(
196
+ atom,
197
+ others: list[Concept | ConceptRef],
198
+ environment: Environment | None = None,
199
+ ):
200
+ if isinstance(atom, (ConceptRef, Concept)):
201
+ # when we are looking at atoms, if there is a concept that is in others
202
+ # return directly
203
+ if atom.address in others:
204
+ return False
205
+ return concept_is_relevant(atom, others, environment)
206
+
207
+ if isinstance(atom, AggregateWrapper) and not atom.by:
208
+ return False
209
+ elif isinstance(atom, AggregateWrapper):
210
+ return any(atom_is_relevant(x, others, environment) for x in atom.by)
211
+
212
+ if isinstance(atom, Function):
213
+ relevant = False
214
+ print("atom args")
215
+ for arg in atom.arguments:
216
+ relevant = relevant or atom_is_relevant(arg, others, environment)
217
+ return relevant
218
+ elif isinstance(atom, FunctionCallWrapper):
219
+ return any(
220
+ [atom_is_relevant(atom.content, others, environment)]
221
+ + [atom_is_relevant(x, others, environment) for x in atom.args]
222
+ )
223
+ elif isinstance(atom, ConceptArgs):
224
+ # use atom is relevant here to trigger the early exit behavior for concpets in set
225
+ return any(
226
+ [atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
227
+ )
228
+ return False
229
+
230
+
194
231
  def concept_is_relevant(
195
232
  concept: Concept | ConceptRef,
196
233
  others: list[Concept | ConceptRef],
197
234
  environment: Environment | None = None,
198
235
  ) -> bool:
199
- if isinstance(concept, UndefinedConcept):
200
236
 
237
+ if isinstance(concept, UndefinedConcept):
201
238
  return False
202
239
  if concept.datatype == DataType.UNKNOWN:
203
240
  return False
241
+
204
242
  if isinstance(concept, ConceptRef):
205
243
  if environment:
206
244
  concept = environment.concepts[concept.address]
@@ -208,41 +246,56 @@ def concept_is_relevant(
208
246
  raise SyntaxError(
209
247
  "Require environment to determine relevance of ConceptRef"
210
248
  )
211
-
249
+ if concept.derivation == Derivation.CONSTANT:
250
+ return False
212
251
  if concept.is_aggregate and not (
213
252
  isinstance(concept.lineage, AggregateWrapper) and concept.lineage.by
214
253
  ):
215
254
 
216
255
  return False
217
256
  if concept.purpose in (Purpose.PROPERTY, Purpose.METRIC) and concept.keys:
218
- if any([c in others for c in concept.keys]):
219
-
257
+ if all([c in others for c in concept.keys]):
220
258
  return False
259
+ if (
260
+ concept.purpose == Purpose.KEY
261
+ and concept.keys
262
+ and all([c in others for c in concept.keys])
263
+ ):
264
+ return False
221
265
  if concept.purpose in (Purpose.METRIC,):
222
266
  if all([c in others for c in concept.grain.components]):
223
267
  return False
224
- if concept.derivation in (Derivation.BASIC,):
225
- return any(
226
- concept_is_relevant(c, others, environment)
227
- for c in concept.concept_arguments
228
- )
268
+ if concept.derivation in (Derivation.BASIC,) and isinstance(
269
+ concept.lineage, Function
270
+ ):
271
+ relevant = False
272
+ for arg in concept.lineage.arguments:
273
+ relevant = atom_is_relevant(arg, others, environment) or relevant
274
+ return relevant
229
275
  if concept.granularity == Granularity.SINGLE_ROW:
230
276
  return False
231
277
  return True
232
278
 
233
279
 
234
280
  def concepts_to_grain_concepts(
235
- concepts: Iterable[Concept | ConceptRef | str], environment: Environment | None
281
+ concepts: Iterable[Concept | ConceptRef | str],
282
+ environment: Environment | None,
283
+ local_concepts: dict[str, Concept] | None = None,
236
284
  ) -> list[Concept]:
237
285
  pconcepts: list[Concept] = []
238
286
  for c in concepts:
239
-
240
287
  if isinstance(c, Concept):
241
288
  pconcepts.append(c)
242
289
  elif isinstance(c, ConceptRef) and environment:
243
- pconcepts.append(environment.concepts[c.address])
290
+ if local_concepts and c.address in local_concepts:
291
+ pconcepts.append(local_concepts[c.address])
292
+ else:
293
+ pconcepts.append(environment.concepts[c.address])
244
294
  elif isinstance(c, str) and environment:
245
- pconcepts.append(environment.concepts[c])
295
+ if local_concepts and c in local_concepts:
296
+ pconcepts.append(local_concepts[c])
297
+ else:
298
+ pconcepts.append(environment.concepts[c])
246
299
  else:
247
300
  raise ValueError(
248
301
  f"Unable to resolve input {c} without environment provided to concepts_to_grain call"
@@ -250,6 +303,7 @@ def concepts_to_grain_concepts(
250
303
 
251
304
  final: List[Concept] = []
252
305
  for sub in pconcepts:
306
+
253
307
  if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
254
308
  continue
255
309
  final.append(sub)
@@ -366,7 +420,12 @@ def function_to_concept(
366
420
  is_metric = False
367
421
  ref_args, is_metric = get_relevant_parent_concepts(parent)
368
422
  concrete_args = [environment.concepts[c.address] for c in ref_args]
369
- pkeys += [x for x in concrete_args if not x.derivation == Derivation.CONSTANT]
423
+ pkeys += [
424
+ x
425
+ for x in concrete_args
426
+ if not x.derivation == Derivation.CONSTANT
427
+ and not (x.derivation == Derivation.AGGREGATE and not x.grain.components)
428
+ ]
370
429
  grain: Grain | None = Grain()
371
430
  for x in pkeys:
372
431
  grain += x.grain
@@ -376,7 +435,7 @@ def function_to_concept(
376
435
  modifiers = get_upstream_modifiers(pkeys, environment)
377
436
  key_grain: list[str] = []
378
437
  for x in pkeys:
379
- # metrics will group to keys, so do no do key traversal
438
+ # metrics will group to keys, so do not do key traversal
380
439
  if is_metric:
381
440
  key_grain.append(x.address)
382
441
  # otherwse, for row ops, assume keys are transitive
@@ -419,7 +478,6 @@ def function_to_concept(
419
478
  else:
420
479
  derivation = Derivation.BASIC
421
480
  granularity = Granularity.MULTI_ROW
422
-
423
481
  if grain is not None:
424
482
  r = Concept(
425
483
  name=name,
@@ -0,0 +1,8 @@
1
+ class ParseError(Exception):
2
+ pass
3
+
4
+
5
+ class NameShadowError(ParseError):
6
+ """
7
+ Raised when a name shadows another name in the same scope.
8
+ """