pytrilogy 0.0.3.7__tar.gz → 0.0.3.9__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 (130) hide show
  1. {pytrilogy-0.0.3.7/pytrilogy.egg-info → pytrilogy-0.0.3.9}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_models.py +6 -2
  5. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_select.py +6 -5
  6. pytrilogy-0.0.3.9/tests/test_user_functions.py +206 -0
  7. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/author.py +155 -63
  9. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/build.py +35 -5
  10. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/environment.py +2 -1
  11. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/group_node.py +1 -1
  12. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/multiselect_node.py +1 -1
  13. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/statements/author.py +2 -0
  14. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/config.py +8 -2
  15. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/dataframe.py +9 -4
  16. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/executor.py +5 -2
  17. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/common.py +88 -56
  18. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/parse_engine.py +53 -33
  19. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/trilogy.lark +6 -4
  20. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/LICENSE.md +0 -0
  21. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/README.md +0 -0
  22. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/pyproject.toml +0 -0
  23. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/pytrilogy.egg-info/dependency_links.txt +0 -0
  24. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/pytrilogy.egg-info/entry_points.txt +0 -0
  25. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/pytrilogy.egg-info/requires.txt +0 -0
  26. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/pytrilogy.egg-info/top_level.txt +0 -0
  27. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/setup.cfg +0 -0
  28. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/setup.py +0 -0
  29. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_datatypes.py +0 -0
  30. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_declarations.py +0 -0
  31. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_derived_concepts.py +0 -0
  32. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_discovery_nodes.py +0 -0
  33. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_enums.py +0 -0
  34. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_environment.py +0 -0
  35. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_executor.py +0 -0
  36. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_functions.py +0 -0
  37. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_imports.py +0 -0
  38. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_metadata.py +0 -0
  39. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_multi_join_assignments.py +0 -0
  40. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_parse_engine.py +0 -0
  41. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_parsing.py +0 -0
  42. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_partial_handling.py +0 -0
  43. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_query_processing.py +0 -0
  44. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_show.py +0 -0
  45. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_statements.py +0 -0
  46. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_undefined_concept.py +0 -0
  47. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/tests/test_where_clause.py +0 -0
  48. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/authoring/__init__.py +0 -0
  49. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/compiler.py +0 -0
  50. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/constants.py +0 -0
  51. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/__init__.py +0 -0
  52. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/constants.py +0 -0
  53. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/enums.py +0 -0
  54. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/env_processor.py +0 -0
  55. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/environment_helpers.py +0 -0
  56. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/ergonomics.py +0 -0
  57. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/exceptions.py +0 -0
  58. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/functions.py +0 -0
  59. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/graph_models.py +0 -0
  60. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/internal.py +0 -0
  61. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/__init__.py +0 -0
  62. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/build_environment.py +0 -0
  63. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/core.py +0 -0
  64. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/datasource.py +0 -0
  65. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/models/execute.py +0 -0
  66. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/optimization.py +0 -0
  67. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/optimizations/__init__.py +0 -0
  68. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/optimizations/base_optimization.py +0 -0
  69. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/optimizations/inline_constant.py +0 -0
  70. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/optimizations/inline_datasource.py +0 -0
  71. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  72. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/__init__.py +0 -0
  73. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  74. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/graph_utils.py +0 -0
  75. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/__init__.py +0 -0
  76. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  77. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/common.py +0 -0
  78. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  79. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  80. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  81. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  82. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  83. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  84. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  85. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/select_node.py +0 -0
  86. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  87. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/union_node.py +0 -0
  88. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  89. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/node_generators/window_node.py +0 -0
  90. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/__init__.py +0 -0
  91. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/base_node.py +0 -0
  92. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/filter_node.py +0 -0
  93. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/group_node.py +0 -0
  94. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/merge_node.py +0 -0
  95. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  96. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/union_node.py +0 -0
  97. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  98. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/nodes/window_node.py +0 -0
  99. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/processing/utility.py +0 -0
  100. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/query_processor.py +0 -0
  101. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/statements/__init__.py +0 -0
  102. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/statements/build.py +0 -0
  103. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/statements/common.py +0 -0
  104. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/core/statements/execute.py +0 -0
  105. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/__init__.py +0 -0
  106. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/base.py +0 -0
  107. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/bigquery.py +0 -0
  108. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/common.py +0 -0
  109. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/duckdb.py +0 -0
  110. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/enums.py +0 -0
  111. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/postgres.py +0 -0
  112. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/presto.py +0 -0
  113. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/snowflake.py +0 -0
  114. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/dialect/sql_server.py +0 -0
  115. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/engine.py +0 -0
  116. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/hooks/__init__.py +0 -0
  117. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/hooks/base_hook.py +0 -0
  118. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/hooks/graph_hook.py +0 -0
  119. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/hooks/query_debugger.py +0 -0
  120. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/metadata/__init__.py +0 -0
  121. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parser.py +0 -0
  122. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/__init__.py +0 -0
  123. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/config.py +0 -0
  124. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/exceptions.py +0 -0
  125. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/helpers.py +0 -0
  126. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/parsing/render.py +0 -0
  127. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/py.typed +0 -0
  128. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/scripts/__init__.py +0 -0
  129. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/scripts/trilogy.py +0 -0
  130. {pytrilogy-0.0.3.7 → pytrilogy-0.0.3.9}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.7
3
+ Version: 0.0.3.9
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.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.7
3
+ Version: 0.0.3.9
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -28,6 +28,7 @@ tests/test_select.py
28
28
  tests/test_show.py
29
29
  tests/test_statements.py
30
30
  tests/test_undefined_concept.py
31
+ tests/test_user_functions.py
31
32
  tests/test_where_clause.py
32
33
  trilogy/__init__.py
33
34
  trilogy/compiler.py
@@ -219,10 +219,14 @@ def test_comparison():
219
219
  # this should not error
220
220
  Comparison(left=1, right=[1, 2, 3], operator=ComparisonOperator.IN)
221
221
 
222
- Comparison(left=1, right=ListWrapper([1, 2, 3]), operator=ComparisonOperator.IN)
222
+ Comparison(
223
+ left=1,
224
+ right=ListWrapper([1, 2, 3], type=DataType.INTEGER),
225
+ operator=ComparisonOperator.IN,
226
+ )
223
227
 
224
228
 
225
- def test_comparison():
229
+ def test_comparison_invalid_type():
226
230
  try:
227
231
  Comparison(left=1, right="abc", operator=ComparisonOperator.EQ)
228
232
  except Exception as exc:
@@ -222,12 +222,14 @@ property id.class int;
222
222
  property id.name string;
223
223
 
224
224
  auto class_count <- count(class);
225
- auto name_count <- count(name);
225
+ auto name_count <- count(name) by class;
226
+
227
+ auto test_ratio <- name_count / sum name_count over class;
226
228
 
227
229
  select
228
230
  class,
229
231
  count(class) -> name_count,
230
- (name_count / sum name_count over class) -> name_class_ratio,
232
+ name_count / sum name_count over class -> name_class_ratio,
231
233
  ;
232
234
  """
233
235
  env, statements = env.parse(q1)
@@ -236,6 +238,5 @@ select
236
238
  assert select.grain.components == {
237
239
  "local.class",
238
240
  }
239
- assert select.local_concepts["local.name_class_ratio"].grain.components == {
240
- "local.class",
241
- }
241
+ benv = env.materialize_for_select()
242
+ assert benv.concepts["test_ratio"].keys == {"local.class", "local.id"}
@@ -0,0 +1,206 @@
1
+ from trilogy import Dialects
2
+ from trilogy.core.enums import Derivation, Purpose
3
+
4
+
5
+ def test_user_function_def():
6
+ x = Dialects.DUCK_DB.default_executor()
7
+
8
+ results = x.execute_query(
9
+ """
10
+ def percent_ratio(a, b, digits=3) -> round(a::float / b * 100, digits);
11
+
12
+
13
+ select @percent_ratio(10, 100) as ratio;
14
+
15
+ """
16
+ )
17
+
18
+ assert results.fetchall()[0].ratio == 10.0
19
+
20
+
21
+ def test_user_function_aggregate():
22
+ x = Dialects.DUCK_DB.default_executor()
23
+
24
+ results = x.execute_query(
25
+ """
26
+ key x int;
27
+ property x.price float;
28
+
29
+ datasource raw_data (
30
+ x: x,
31
+ price: price
32
+ )
33
+ grain (x)
34
+ query '''
35
+ select 1 as x, 2.0 as price
36
+ union all
37
+ select 2 as x, 3.0 as price
38
+ union all
39
+ select 10 as x, 5.0 as price
40
+ ''';
41
+
42
+ def sum_times(a)-> a * sum(x);
43
+
44
+
45
+ select @sum_times(10) as total;
46
+
47
+ """
48
+ )
49
+
50
+ assert results.fetchall()[0].total == 130
51
+
52
+
53
+ def test_user_function_nested():
54
+ x = Dialects.DUCK_DB.default_executor()
55
+
56
+ results = x.execute_query(
57
+ """
58
+ key x int;
59
+ property x.price float;
60
+
61
+ datasource raw_data (
62
+ x: x,
63
+ price: price
64
+ )
65
+ grain (x)
66
+ query '''
67
+ select 1 as x, 2.0 as price
68
+ union all
69
+ select 2 as x, 3.0 as price
70
+ union all
71
+ select 10 as x, 5.0 as price
72
+ ''';
73
+
74
+ def sum_times(a)-> a * sum(x + price);
75
+
76
+
77
+ select @sum_times(10) as total;
78
+
79
+ """
80
+ )
81
+
82
+ assert results.fetchall()[0].total == 230
83
+
84
+
85
+ def test_user_function_nested_rowset():
86
+ x = Dialects.DUCK_DB.default_executor()
87
+
88
+ results = x.execute_query(
89
+ """
90
+ key x int;
91
+ property x.price float;
92
+
93
+ datasource raw_data (
94
+ x: x,
95
+ price: price
96
+ )
97
+ grain (x)
98
+ query '''
99
+ select 1 as x, 2.0 as price
100
+ union all
101
+ select 2 as x, 3.0 as price
102
+ union all
103
+ select 10 as x, 5.0 as price
104
+ ''';
105
+
106
+ def sum_times(a)-> a * sum(x + price);
107
+
108
+ with rowset as
109
+ select @sum_times(10) as total;
110
+
111
+ select rowset.total;
112
+
113
+ """
114
+ )
115
+
116
+ assert results.fetchall()[0].rowset_total == 230
117
+
118
+
119
+ def test_user_function_case():
120
+ from trilogy.hooks.query_debugger import DebuggingHook
121
+
122
+ DebuggingHook()
123
+ x = Dialects.DUCK_DB.default_executor()
124
+
125
+ results = x.execute_query(
126
+ """
127
+ key x int;
128
+ key y int;
129
+ property x.price float;
130
+
131
+ datasource raw_data (
132
+ x: x,
133
+ price: price
134
+ )
135
+ grain (x)
136
+ query '''
137
+ select 1 as x, 2.0 as price
138
+ union all
139
+ select 2 as x, 3.0 as price
140
+ union all
141
+ select 10 as x, 5.0 as price
142
+ ''';
143
+
144
+ datasource join_x_y (
145
+ x: x,
146
+ y: y)
147
+ grain (x, y)
148
+ query '''
149
+ select 1 as x, 1 as y
150
+ union all
151
+ select 2 as x, 1 as y
152
+ union all
153
+ select 10 as x, 2 as y
154
+ ''';
155
+
156
+ def weekday_sales(weekday) ->
157
+ SUM(CASE WHEN 10 = weekday THEN x ELSE 0 END) +
158
+ SUM(CASE WHEN 10 = weekday THEN price ELSE 0.0 END)
159
+ ;
160
+
161
+
162
+ select
163
+ y,
164
+ @weekday_sales(10) -> test
165
+ order by y asc;
166
+
167
+ """
168
+ )
169
+ results = results.fetchall()
170
+ assert results[0].test == 8
171
+ assert results[1].test == 15
172
+
173
+
174
+ def test_parsing():
175
+ x = Dialects.DUCK_DB.default_executor()
176
+ x.execute_query(
177
+ """
178
+ key x int;
179
+ property x.price float;
180
+
181
+ datasource raw_data (
182
+ x: x,
183
+ price: price
184
+ )
185
+ grain (x)
186
+ query '''
187
+ select 1 as x, 2.0 as price
188
+ union all
189
+ select 2 as x, 3.0 as price
190
+ union all
191
+ select 10 as x, 5.0 as price
192
+ ''';
193
+
194
+
195
+ auto test <-SUM(CASE WHEN 10 = weekday THEN x ELSE 0 END) +
196
+ SUM(CASE WHEN 10 = weekday THEN price ELSE 0.0 END);
197
+
198
+
199
+
200
+
201
+ """
202
+ )
203
+ test = x.environment.concepts["test"]
204
+ assert test.keys == set()
205
+ assert test.purpose == Purpose.METRIC
206
+ assert test.derivation == Derivation.BASIC
@@ -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.7"
7
+ __version__ = "0.0.3.9"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -75,6 +75,9 @@ class Mergeable(ABC):
75
75
  def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
76
76
  raise NotImplementedError
77
77
 
78
+ def with_reference_replacement(self, source: str, target: Expr):
79
+ raise NotImplementedError(type(self))
80
+
78
81
 
79
82
  class ConceptArgs(ABC):
80
83
  @property
@@ -152,6 +155,11 @@ class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
152
155
  metadata=self.metadata,
153
156
  )
154
157
 
158
+ def with_reference_replacement(self, source: str, target: Expr):
159
+ if self.address == source:
160
+ return target
161
+ return self
162
+
155
163
 
156
164
  class UndefinedConcept(ConceptRef):
157
165
  pass
@@ -409,7 +417,7 @@ class Grain(Namespaced, BaseModel):
409
417
  ) -> Grain:
410
418
  from trilogy.parsing.common import concepts_to_grain_concepts
411
419
 
412
- return Grain.model_construct(
420
+ x = Grain.model_construct(
413
421
  components={
414
422
  c.address
415
423
  for c in concepts_to_grain_concepts(concepts, environment=environment)
@@ -417,6 +425,8 @@ class Grain(Namespaced, BaseModel):
417
425
  where_clause=where_clause,
418
426
  )
419
427
 
428
+ return x
429
+
420
430
  def with_namespace(self, namespace: str) -> "Grain":
421
431
  return Grain.model_construct(
422
432
  components={address_with_namespace(c, namespace) for c in self.components},
@@ -537,6 +547,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
537
547
  MagicConstants,
538
548
  WindowItem,
539
549
  AggregateWrapper,
550
+ FilterItem,
540
551
  ]
541
552
  right: Union[
542
553
  int,
@@ -556,6 +567,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
556
567
  WindowItem,
557
568
  AggregateWrapper,
558
569
  TupleWrapper,
570
+ FilterItem,
559
571
  ]
560
572
  operator: ComparisonOperator
561
573
 
@@ -662,6 +674,21 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
662
674
  operator=self.operator,
663
675
  )
664
676
 
677
+ def with_reference_replacement(self, source, target):
678
+ return self.__class__.model_construct(
679
+ left=(
680
+ self.left.with_reference_replacement(source, target)
681
+ if isinstance(self.left, Mergeable)
682
+ else self.left
683
+ ),
684
+ right=(
685
+ self.right.with_reference_replacement(source, target)
686
+ if isinstance(self.right, Mergeable)
687
+ else self.right
688
+ ),
689
+ operator=self.operator,
690
+ )
691
+
665
692
  def with_namespace(self, namespace: str):
666
693
  return self.__class__.model_construct(
667
694
  left=(
@@ -1224,7 +1251,7 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1224
1251
  def with_merge(
1225
1252
  self, source: Concept, target: Concept, modifiers: List[Modifier]
1226
1253
  ) -> "OrderItem":
1227
- return OrderItem(
1254
+ return OrderItem.model_construct(
1228
1255
  expr=(
1229
1256
  self.expr.with_merge(source, target, modifiers)
1230
1257
  if isinstance(self.expr, Mergeable)
@@ -1233,19 +1260,19 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1233
1260
  order=self.order,
1234
1261
  )
1235
1262
 
1236
- @property
1237
- def output(self):
1238
- return self.expr.output
1263
+ def with_reference_replacement(self, source, target):
1264
+ return OrderItem.model_construct(
1265
+ expr=(
1266
+ self.expr.with_reference_replacement(source, target)
1267
+ if isinstance(self.expr, Mergeable)
1268
+ else self.expr
1269
+ ),
1270
+ order=self.order,
1271
+ )
1239
1272
 
1240
1273
  @property
1241
1274
  def concept_arguments(self) -> Sequence[ConceptRef]:
1242
- base: List[ConceptRef] = []
1243
- x = self.expr
1244
- if isinstance(x, ConceptRef):
1245
- base += [x]
1246
- elif isinstance(x, ConceptArgs):
1247
- base += x.concept_arguments
1248
- return base
1275
+ return get_concept_arguments(self.expr)
1249
1276
 
1250
1277
  @property
1251
1278
  def row_arguments(self) -> Sequence[ConceptRef]:
@@ -1305,6 +1332,17 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1305
1332
  )
1306
1333
  return output
1307
1334
 
1335
+ def with_reference_replacement(self, source, target):
1336
+ return WindowItem.model_construct(
1337
+ type=self.type,
1338
+ content=self.content.with_reference_replacement(source, target),
1339
+ over=[x.with_reference_replacement(source, target) for x in self.over],
1340
+ order_by=[
1341
+ x.with_reference_replacement(source, target) for x in self.order_by
1342
+ ],
1343
+ index=self.index,
1344
+ )
1345
+
1308
1346
  def with_namespace(self, namespace: str) -> "WindowItem":
1309
1347
  return WindowItem.model_construct(
1310
1348
  type=self.type,
@@ -1316,31 +1354,19 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1316
1354
 
1317
1355
  @property
1318
1356
  def concept_arguments(self) -> List[ConceptRef]:
1319
- return self.arguments
1320
-
1321
- @property
1322
- def arguments(self) -> List[ConceptRef]:
1323
1357
  output = [self.content]
1324
1358
  for order in self.order_by:
1325
- output += [order.output]
1359
+ output += get_concept_arguments(order)
1326
1360
  for item in self.over:
1327
- output += [item]
1361
+ output += get_concept_arguments(item)
1328
1362
  return output
1329
1363
 
1330
- @property
1331
- def output(self) -> ConceptRef:
1332
- return self.content
1333
-
1334
1364
  @property
1335
1365
  def output_datatype(self):
1336
1366
  if self.type in (WindowType.RANK, WindowType.ROW_NUMBER):
1337
1367
  return DataType.INTEGER
1338
1368
  return self.content.output_datatype
1339
1369
 
1340
- @property
1341
- def output_purpose(self):
1342
- return Purpose.PROPERTY
1343
-
1344
1370
 
1345
1371
  def get_basic_type(
1346
1372
  type: DataType | ListType | StructType | MapType | NumericType,
@@ -1407,12 +1433,28 @@ class CaseWhen(Namespaced, ConceptArgs, Mergeable, BaseModel):
1407
1433
  ),
1408
1434
  )
1409
1435
 
1436
+ def with_reference_replacement(self, source, target):
1437
+ return CaseWhen.model_construct(
1438
+ comparison=self.comparison.with_reference_replacement(source, target),
1439
+ expr=(
1440
+ self.expr.with_reference_replacement(source, target)
1441
+ if isinstance(self.expr, Mergeable)
1442
+ else self.expr
1443
+ ),
1444
+ )
1445
+
1410
1446
 
1411
1447
  class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
1412
1448
  expr: "Expr"
1413
1449
  # this ensures that it's easily differentiable from CaseWhen
1414
1450
  discriminant: ComparisonOperator = ComparisonOperator.ELSE
1415
1451
 
1452
+ def __str__(self):
1453
+ return self.__repr__()
1454
+
1455
+ def __repr__(self):
1456
+ return f"ELSE {str(self.expr)}"
1457
+
1416
1458
  @field_validator("expr", mode="before")
1417
1459
  def enforce_expr(cls, v):
1418
1460
  if isinstance(v, Concept):
@@ -1435,6 +1477,19 @@ class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
1435
1477
  ),
1436
1478
  )
1437
1479
 
1480
+ def with_reference_replacement(self, source, target):
1481
+ return CaseElse.model_construct(
1482
+ discriminant=self.discriminant,
1483
+ expr=(
1484
+ self.expr.with_reference_replacement(
1485
+ source,
1486
+ target,
1487
+ )
1488
+ if isinstance(self.expr, Mergeable)
1489
+ else self.expr
1490
+ ),
1491
+ )
1492
+
1438
1493
  def with_namespace(self, namespace: str) -> CaseElse:
1439
1494
  return CaseElse.model_construct(
1440
1495
  discriminant=self.discriminant,
@@ -1483,35 +1538,10 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1483
1538
  List[Set[DataType]],
1484
1539
  ]
1485
1540
  ] = None
1486
- arguments: Sequence[
1487
- Union[
1488
- ConceptRef,
1489
- AggregateWrapper,
1490
- Function,
1491
- Parenthetical,
1492
- CaseWhen,
1493
- CaseElse,
1494
- WindowItem,
1495
- int,
1496
- float,
1497
- str,
1498
- date,
1499
- datetime,
1500
- MapWrapper[Any, Any],
1501
- DataType,
1502
- ListType,
1503
- MapType,
1504
- NumericType,
1505
- DatePart,
1506
- list,
1507
- ListWrapper[Any],
1508
- ]
1509
- ]
1541
+ arguments: Sequence[FuncArgs]
1510
1542
 
1511
1543
  def __init__(self, **kwargs):
1512
1544
  super().__init__(**kwargs)
1513
- if "datatype" in str(self):
1514
- raise SyntaxError(str(self))
1515
1545
 
1516
1546
  def __repr__(self):
1517
1547
  return f'{self.operator.value}({",".join([str(a) for a in self.arguments])})'
@@ -1597,6 +1627,29 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1597
1627
  )
1598
1628
  return v
1599
1629
 
1630
+ def with_reference_replacement(self, source: str, target: Expr):
1631
+ return Function.model_construct(
1632
+ operator=self.operator,
1633
+ arguments=[
1634
+ (
1635
+ c.with_reference_replacement(
1636
+ source,
1637
+ target,
1638
+ )
1639
+ if isinstance(
1640
+ c,
1641
+ Mergeable,
1642
+ )
1643
+ else c
1644
+ )
1645
+ for c in self.arguments
1646
+ ],
1647
+ output_datatype=self.output_datatype,
1648
+ output_purpose=self.output_purpose,
1649
+ valid_inputs=self.valid_inputs,
1650
+ arg_count=self.arg_count,
1651
+ )
1652
+
1600
1653
  def with_namespace(self, namespace: str) -> "Function":
1601
1654
  return Function.model_construct(
1602
1655
  operator=self.operator,
@@ -1658,10 +1711,13 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1658
1711
  return base_grain
1659
1712
 
1660
1713
 
1661
- class AggregateWrapper(Mergeable, ConceptArgs, Namespaced, BaseModel):
1714
+ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel):
1662
1715
  function: Function
1663
1716
  by: List[ConceptRef] = Field(default_factory=list)
1664
1717
 
1718
+ def __init__(self, **kwargs):
1719
+ super().__init__(**kwargs)
1720
+
1665
1721
  @field_validator("by", mode="before")
1666
1722
  @classmethod
1667
1723
  def enforce_concept_ref(cls, v):
@@ -1693,10 +1749,6 @@ class AggregateWrapper(Mergeable, ConceptArgs, Namespaced, BaseModel):
1693
1749
  def output_purpose(self):
1694
1750
  return self.function.output_purpose
1695
1751
 
1696
- @property
1697
- def arguments(self):
1698
- return self.function.arguments
1699
-
1700
1752
  def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
1701
1753
  return AggregateWrapper.model_construct(
1702
1754
  function=self.function.with_merge(source, target, modifiers=modifiers),
@@ -1707,6 +1759,16 @@ class AggregateWrapper(Mergeable, ConceptArgs, Namespaced, BaseModel):
1707
1759
  ),
1708
1760
  )
1709
1761
 
1762
+ def with_reference_replacement(self, source, target):
1763
+ return AggregateWrapper.model_construct(
1764
+ function=self.function.with_reference_replacement(source, target),
1765
+ by=(
1766
+ [c.with_reference_replacement(source, target) for c in self.by]
1767
+ if self.by
1768
+ else []
1769
+ ),
1770
+ )
1771
+
1710
1772
  def with_namespace(self, namespace: str) -> "AggregateWrapper":
1711
1773
  return AggregateWrapper.model_construct(
1712
1774
  function=self.function.with_namespace(namespace),
@@ -1796,11 +1858,6 @@ class RowsetItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1796
1858
  rowset=self.rowset.with_namespace(namespace),
1797
1859
  )
1798
1860
 
1799
- @property
1800
- def arguments(self) -> List[ConceptRef]:
1801
- output = [self.content]
1802
- return output
1803
-
1804
1861
  @property
1805
1862
  def output(self) -> ConceptRef:
1806
1863
  return self.content
@@ -1831,7 +1888,10 @@ class OrderBy(Mergeable, Namespaced, BaseModel):
1831
1888
 
1832
1889
  @property
1833
1890
  def concept_arguments(self):
1834
- return [x.expr for x in self.items]
1891
+ base = []
1892
+ for x in self.items:
1893
+ base += x.concept_arguments
1894
+ return base
1835
1895
 
1836
1896
 
1837
1897
  class AlignClause(Namespaced, BaseModel):
@@ -2088,6 +2148,11 @@ class Comment(BaseModel):
2088
2148
  text: str
2089
2149
 
2090
2150
 
2151
+ class ArgBinding(BaseModel):
2152
+ name: str
2153
+ default: Expr | None = None
2154
+
2155
+
2091
2156
  Expr = (
2092
2157
  MagicConstants
2093
2158
  | bool
@@ -2099,6 +2164,7 @@ Expr = (
2099
2164
  | datetime
2100
2165
  | TupleWrapper
2101
2166
  | ListWrapper
2167
+ | MapWrapper
2102
2168
  | WindowItem
2103
2169
  | FilterItem
2104
2170
  | ConceptRef
@@ -2107,4 +2173,30 @@ Expr = (
2107
2173
  | Parenthetical
2108
2174
  | Function
2109
2175
  | AggregateWrapper
2176
+ | CaseWhen
2177
+ | CaseElse
2178
+ )
2179
+
2180
+ FuncArgs = (
2181
+ ConceptRef
2182
+ | AggregateWrapper
2183
+ | Function
2184
+ | Parenthetical
2185
+ | CaseWhen
2186
+ | CaseElse
2187
+ | WindowItem
2188
+ | FilterItem
2189
+ | int
2190
+ | float
2191
+ | str
2192
+ | date
2193
+ | datetime
2194
+ | MapWrapper[Any, Any]
2195
+ | DataType
2196
+ | ListType
2197
+ | MapType
2198
+ | NumericType
2199
+ | DatePart
2200
+ | list
2201
+ | ListWrapper[Any]
2110
2202
  )