pytrilogy 0.0.3.86__tar.gz → 0.0.3.88__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 (151) hide show
  1. {pytrilogy-0.0.3.86/pytrilogy.egg-info → pytrilogy-0.0.3.88}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/__init__.py +1 -1
  4. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/enums.py +9 -0
  5. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/functions.py +31 -1
  6. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/author.py +15 -0
  7. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/build.py +5 -2
  8. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/core.py +20 -3
  9. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/execute.py +5 -0
  10. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/base.py +33 -14
  11. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/presto.py +2 -1
  12. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/parse_engine.py +36 -6
  13. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/trilogy.lark +13 -4
  14. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/LICENSE.md +0 -0
  15. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/README.md +0 -0
  16. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/pyproject.toml +0 -0
  17. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/pytrilogy.egg-info/SOURCES.txt +0 -0
  18. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/pytrilogy.egg-info/dependency_links.txt +0 -0
  19. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/pytrilogy.egg-info/entry_points.txt +0 -0
  20. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/pytrilogy.egg-info/requires.txt +0 -0
  21. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/pytrilogy.egg-info/top_level.txt +0 -0
  22. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/setup.cfg +0 -0
  23. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/setup.py +0 -0
  24. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_datatypes.py +0 -0
  25. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_declarations.py +0 -0
  26. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_derived_concepts.py +0 -0
  27. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_discovery_nodes.py +0 -0
  28. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_enums.py +0 -0
  29. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_environment.py +0 -0
  30. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_execute_models.py +0 -0
  31. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_executor.py +0 -0
  32. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_failure.py +0 -0
  33. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_functions.py +0 -0
  34. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_imports.py +0 -0
  35. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_metadata.py +0 -0
  36. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_models.py +0 -0
  37. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_multi_join_assignments.py +0 -0
  38. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_parse_engine.py +0 -0
  39. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_parsing.py +0 -0
  40. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_parsing_failures.py +0 -0
  41. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_partial_handling.py +0 -0
  42. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_query_processing.py +0 -0
  43. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_query_render.py +0 -0
  44. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_select.py +0 -0
  45. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_show.py +0 -0
  46. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_statements.py +0 -0
  47. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_typing.py +0 -0
  48. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_undefined_concept.py +0 -0
  49. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_user_functions.py +0 -0
  50. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/tests/test_where_clause.py +0 -0
  51. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/authoring/__init__.py +0 -0
  52. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/compiler.py +0 -0
  53. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/constants.py +0 -0
  54. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/__init__.py +0 -0
  55. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/constants.py +0 -0
  56. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/env_processor.py +0 -0
  57. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/environment_helpers.py +0 -0
  58. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/ergonomics.py +0 -0
  59. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/exceptions.py +0 -0
  60. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/graph_models.py +0 -0
  61. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/internal.py +0 -0
  62. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/__init__.py +0 -0
  63. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/build_environment.py +0 -0
  64. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/datasource.py +0 -0
  65. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/models/environment.py +0 -0
  66. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/optimization.py +0 -0
  67. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/optimizations/__init__.py +0 -0
  68. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/optimizations/base_optimization.py +0 -0
  69. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/optimizations/inline_datasource.py +0 -0
  70. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  71. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/__init__.py +0 -0
  72. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  73. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/discovery_loop.py +0 -0
  74. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/discovery_node_factory.py +0 -0
  75. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/discovery_utility.py +0 -0
  76. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/discovery_validation.py +0 -0
  77. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/graph_utils.py +0 -0
  78. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/__init__.py +0 -0
  79. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  80. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/common.py +0 -0
  81. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  82. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  83. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/group_node.py +0 -0
  84. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  85. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  86. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  87. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  88. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  89. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  90. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  91. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  92. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/select_node.py +0 -0
  93. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  94. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/union_node.py +0 -0
  95. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  96. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/node_generators/window_node.py +0 -0
  97. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/__init__.py +0 -0
  98. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/base_node.py +0 -0
  99. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/filter_node.py +0 -0
  100. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/group_node.py +0 -0
  101. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/merge_node.py +0 -0
  102. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  103. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  104. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/union_node.py +0 -0
  105. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  106. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/nodes/window_node.py +0 -0
  107. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/processing/utility.py +0 -0
  108. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/query_processor.py +0 -0
  109. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/statements/__init__.py +0 -0
  110. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/statements/author.py +0 -0
  111. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/statements/build.py +0 -0
  112. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/statements/common.py +0 -0
  113. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/statements/execute.py +0 -0
  114. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/core/utility.py +0 -0
  115. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/__init__.py +0 -0
  116. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/bigquery.py +0 -0
  117. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/common.py +0 -0
  118. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/config.py +0 -0
  119. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/dataframe.py +0 -0
  120. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/duckdb.py +0 -0
  121. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/enums.py +0 -0
  122. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/postgres.py +0 -0
  123. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/snowflake.py +0 -0
  124. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/dialect/sql_server.py +0 -0
  125. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/engine.py +0 -0
  126. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/executor.py +0 -0
  127. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/hooks/__init__.py +0 -0
  128. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/hooks/base_hook.py +0 -0
  129. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/hooks/graph_hook.py +0 -0
  130. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/hooks/query_debugger.py +0 -0
  131. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/metadata/__init__.py +0 -0
  132. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parser.py +0 -0
  133. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/__init__.py +0 -0
  134. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/common.py +0 -0
  135. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/config.py +0 -0
  136. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/exceptions.py +0 -0
  137. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/helpers.py +0 -0
  138. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/parsing/render.py +0 -0
  139. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/py.typed +0 -0
  140. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/render.py +0 -0
  141. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/scripts/__init__.py +0 -0
  142. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/scripts/trilogy.py +0 -0
  143. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/__init__.py +0 -0
  144. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/date.preql +0 -0
  145. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/display.preql +0 -0
  146. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/geography.preql +0 -0
  147. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/money.preql +0 -0
  148. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/net.preql +0 -0
  149. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/ranking.preql +0 -0
  150. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/std/report.preql +0 -0
  151. {pytrilogy-0.0.3.86 → pytrilogy-0.0.3.88}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.86
3
+ Version: 0.0.3.88
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.86
3
+ Version: 0.0.3.88
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -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.86"
7
+ __version__ = "0.0.3.88"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -13,6 +13,11 @@ class UnnestMode(Enum):
13
13
  SNOWFLAKE = "snowflake"
14
14
 
15
15
 
16
+ class GroupMode(Enum):
17
+ AUTO = "auto"
18
+ BY_INDEX = "by_index"
19
+
20
+
16
21
  class ConceptSource(Enum):
17
22
  MANUAL = "manual"
18
23
  CTE = "cte"
@@ -156,6 +161,10 @@ class FunctionType(Enum):
156
161
  ARRAY_TRANSFORM = "array_transform"
157
162
  ARRAY_TO_STRING = "array_to_string"
158
163
 
164
+ # MAP
165
+ MAP_KEYS = "map_keys"
166
+ MAP_VALUES = "map_values"
167
+
159
168
  # TEXT AND MAYBE MORE
160
169
  SPLIT = "split"
161
170
  LENGTH = "len"
@@ -70,7 +70,7 @@ def get_coalesce_output_type(args: list[Any]) -> CONCRETE_TYPES:
70
70
 
71
71
 
72
72
  def get_transform_output_type(args: list[Any]) -> CONCRETE_TYPES:
73
- return arg_to_datatype(args[2])
73
+ return ArrayType(type=arg_to_datatype(args[2]))
74
74
 
75
75
 
76
76
  def get_index_output_type(
@@ -178,6 +178,20 @@ def get_date_trunc_output(
178
178
  raise InvalidSyntaxException(f"Date truncation not supported for {target}")
179
179
 
180
180
 
181
+ def get_map_key_type(arg):
182
+ arg_datatype = arg_to_datatype(arg)
183
+ if isinstance(arg_datatype, MapType):
184
+ return ArrayType(type=arg_datatype.key_data_type)
185
+ return ArrayType(type=DataType.STRING)
186
+
187
+
188
+ def get_map_value_type(arg):
189
+ arg_datatype = arg_to_datatype(arg)
190
+ if isinstance(arg_datatype, MapType):
191
+ return ArrayType(type=arg_datatype.value_data_type)
192
+ return ArrayType(type=DataType.STRING)
193
+
194
+
181
195
  FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
182
196
  FunctionType.ALIAS: FunctionConfig(
183
197
  arg_count=1,
@@ -254,6 +268,22 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
254
268
  output_type_function=get_index_output_type,
255
269
  arg_count=2,
256
270
  ),
271
+ FunctionType.MAP_KEYS: FunctionConfig(
272
+ valid_inputs={
273
+ DataType.MAP,
274
+ },
275
+ output_purpose=Purpose.PROPERTY,
276
+ output_type_function=lambda args: get_map_key_type(args[0]),
277
+ arg_count=1,
278
+ ),
279
+ FunctionType.MAP_VALUES: FunctionConfig(
280
+ valid_inputs={
281
+ DataType.MAP,
282
+ },
283
+ output_purpose=Purpose.PROPERTY,
284
+ output_type_function=lambda args: get_map_value_type(args[0]),
285
+ arg_count=1,
286
+ ),
257
287
  FunctionType.ARRAY_DISTINCT: FunctionConfig(
258
288
  valid_inputs={
259
289
  DataType.ARRAY,
@@ -164,6 +164,21 @@ class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
164
164
  def with_reference_replacement(self, source: str, target: Expr | ArgBinding):
165
165
  if self.address == source:
166
166
  return target
167
+
168
+ # a reference might be to an attribute of a struct that is bound late
169
+ # if the replacement is a parent in the access path; replace reference
170
+ # with an attribute access call
171
+ candidates = [f"local.{self.address}", self.address]
172
+ for candidate in candidates:
173
+ if not candidate.startswith(f"{source}."):
174
+ continue
175
+ return Function(
176
+ arguments=[target, self.address.rsplit(".", 1)[1]],
177
+ operator=FunctionType.ATTR_ACCESS,
178
+ arg_count=2,
179
+ output_datatype=arg_to_datatype(target),
180
+ output_purpose=Purpose.PROPERTY,
181
+ )
167
182
  return self
168
183
 
169
184
 
@@ -1649,8 +1649,11 @@ class Factory:
1649
1649
  full = self.local_concepts[base.address]
1650
1650
  if isinstance(full, BuildConcept):
1651
1651
  return full
1652
- raw = self.environment.concepts[base.address]
1653
- return self.build(raw)
1652
+ if base.address in self.environment.concepts:
1653
+ raw = self.environment.concepts[base.address]
1654
+ return self.build(raw)
1655
+ # this will error by design - TODO - more helpful message?
1656
+ return self.build(self.environment.concepts[base.address])
1654
1657
 
1655
1658
  @build.register
1656
1659
  def _(self, base: CaseWhen) -> BuildCaseWhen:
@@ -27,7 +27,7 @@ from pydantic_core import core_schema
27
27
  from trilogy.constants import (
28
28
  MagicConstants,
29
29
  )
30
- from trilogy.core.enums import Ordering
30
+ from trilogy.core.enums import Modifier, Ordering
31
31
 
32
32
 
33
33
  class DataTyped(ABC):
@@ -211,9 +211,19 @@ class MapType(BaseModel):
211
211
  return self.key_type
212
212
 
213
213
 
214
+ class StructComponent(BaseModel):
215
+ name: str
216
+ type: TYPEDEF_TYPES
217
+ modifiers: list[Modifier] = []
218
+
219
+ @field_validator("type", mode="plain")
220
+ def validate_Type(cls, v):
221
+ return v
222
+
223
+
214
224
  class StructType(BaseModel):
215
- fields: Sequence[TYPEDEF_TYPES]
216
- fields_map: Dict[str, DataTyped | int | float | str]
225
+ fields: Sequence[StructComponent | TYPEDEF_TYPES]
226
+ fields_map: Dict[str, DataTyped | int | float | str | StructComponent]
217
227
 
218
228
  @field_validator("fields", mode="plain")
219
229
  def validate_type(cls, v):
@@ -234,6 +244,9 @@ class StructType(BaseModel):
234
244
  def value(self):
235
245
  return self.data_type.value
236
246
 
247
+ def __hash__(self):
248
+ return hash(str(self))
249
+
237
250
 
238
251
  class ListWrapper(Generic[VT], UserList):
239
252
  """Used to distinguish parsed list objects from other lists"""
@@ -401,6 +414,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
401
414
  return DataType.STRING
402
415
  elif isinstance(arg, float):
403
416
  return DataType.FLOAT
417
+ elif isinstance(arg, DataType):
418
+ return arg
404
419
  elif isinstance(arg, NumericType):
405
420
  return arg
406
421
  elif isinstance(arg, TraitDataType):
@@ -420,6 +435,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
420
435
  return DataType.DATETIME
421
436
  elif isinstance(arg, date):
422
437
  return DataType.DATE
438
+ elif isinstance(arg, StructComponent):
439
+ return arg_to_datatype(arg.type)
423
440
  else:
424
441
  raise ValueError(
425
442
  f"Cannot parse arg datatype for arg of raw type {type(arg)} value {arg}"
@@ -1076,6 +1076,11 @@ class UnionCTE(BaseModel):
1076
1076
  def group_to_grain(self) -> bool:
1077
1077
  return False
1078
1078
 
1079
+ @property
1080
+ def group_concepts(self) -> List[BuildConcept]:
1081
+ # unions should always be on unique sets
1082
+ return []
1083
+
1079
1084
  def __add__(self, other):
1080
1085
  if not isinstance(other, UnionCTE) or not other.name == self.name:
1081
1086
  raise SyntaxError("Cannot merge union CTEs")
@@ -15,6 +15,7 @@ from trilogy.core.enums import (
15
15
  ComparisonOperator,
16
16
  DatePart,
17
17
  FunctionType,
18
+ GroupMode,
18
19
  Ordering,
19
20
  ShowCategory,
20
21
  UnnestMode,
@@ -186,6 +187,9 @@ FUNCTION_MAP = {
186
187
  FunctionType.ARRAY: lambda x: f"[{', '.join(x)}]",
187
188
  FunctionType.DATE_LITERAL: lambda x: f"date '{x}'",
188
189
  FunctionType.DATETIME_LITERAL: lambda x: f"datetime '{x}'",
190
+ # MAP
191
+ FunctionType.MAP_KEYS: lambda x: f"map_keys({x[0]})",
192
+ FunctionType.MAP_VALUES: lambda x: f"map_values({x[0]})",
189
193
  # ARRAY
190
194
  FunctionType.ARRAY_SUM: lambda x: f"array_sum({x[0]})",
191
195
  FunctionType.ARRAY_DISTINCT: lambda x: f"array_distinct({x[0]})",
@@ -338,6 +342,7 @@ class BaseDialect:
338
342
  DATATYPE_MAP = DATATYPE_MAP
339
343
  COMPLEX_DATATYPE_MAP = COMPLEX_DATATYPE_MAP
340
344
  UNNEST_MODE = UnnestMode.CROSS_APPLY
345
+ GROUP_MODE = GroupMode.AUTO
341
346
 
342
347
  def __init__(self, rendering: Rendering | None = None):
343
348
  self.rendering = rendering or CONFIG.rendering
@@ -775,6 +780,33 @@ class BaseDialect:
775
780
  else:
776
781
  raise ValueError(f"Unable to render type {type(e)} {e}")
777
782
 
783
+ def render_cte_group_by(
784
+ self, cte: CTE | UnionCTE, select_columns
785
+ ) -> Optional[list[str]]:
786
+
787
+ if not cte.group_to_grain:
788
+ return None
789
+ base = set(
790
+ [self.render_concept_sql(c, cte, alias=False) for c in cte.group_concepts]
791
+ )
792
+ if self.GROUP_MODE == GroupMode.AUTO:
793
+ return sorted(list(base))
794
+
795
+ else:
796
+ # find the index of each column in the select columns
797
+ final = []
798
+ found = []
799
+ for idx, c in enumerate(select_columns):
800
+ pre_alias = c.split(" as ")[0]
801
+ if pre_alias in base:
802
+ final.append(str(idx + 1))
803
+ found.append(pre_alias)
804
+ if not all(c in found for c in base):
805
+ raise ValueError(
806
+ f"Group by columns {base} not found in select columns {select_columns}"
807
+ )
808
+ return final
809
+
778
810
  def render_cte(self, cte: CTE | UnionCTE, auto_sort: bool = True) -> CompiledCTE:
779
811
  if isinstance(cte, UnionCTE):
780
812
  base_statement = f"\n{cte.operator}\n".join(
@@ -926,20 +958,7 @@ class BaseDialect:
926
958
  if cte.order_by
927
959
  else None
928
960
  ),
929
- group_by=(
930
- sorted(
931
- list(
932
- set(
933
- [
934
- self.render_concept_sql(c, cte, alias=False)
935
- for c in cte.group_concepts
936
- ]
937
- )
938
- )
939
- )
940
- if cte.group_to_grain
941
- else None
942
- ),
961
+ group_by=self.render_cte_group_by(cte, select_columns),
943
962
  ),
944
963
  )
945
964
 
@@ -2,7 +2,7 @@ from typing import Any, Callable, Mapping
2
2
 
3
3
  from jinja2 import Template
4
4
 
5
- from trilogy.core.enums import FunctionType, UnnestMode, WindowType
5
+ from trilogy.core.enums import FunctionType, GroupMode, UnnestMode, WindowType
6
6
  from trilogy.core.models.core import DataType
7
7
  from trilogy.dialect.base import BaseDialect
8
8
 
@@ -94,6 +94,7 @@ class PrestoDialect(BaseDialect):
94
94
  DataType.STRING: "VARCHAR",
95
95
  }
96
96
  UNNEST_MODE = UnnestMode.PRESTO
97
+ GROUP_MODE = GroupMode.BY_INDEX
97
98
 
98
99
 
99
100
  class TrinoDialect(PrestoDialect):
@@ -89,6 +89,7 @@ from trilogy.core.models.core import (
89
89
  MapType,
90
90
  MapWrapper,
91
91
  NumericType,
92
+ StructComponent,
92
93
  StructType,
93
94
  TraitDataType,
94
95
  TupleWrapper,
@@ -443,20 +444,39 @@ class ParseToObjects(Transformer):
443
444
  def MINUS(self, args) -> str:
444
445
  return "-"
445
446
 
447
+ @v_args(meta=True)
448
+ def struct_component(self, meta: Meta, args) -> StructComponent:
449
+ modifiers = []
450
+ for arg in args:
451
+ if isinstance(arg, Modifier):
452
+ modifiers.append(arg)
453
+ return StructComponent(name=args[0], type=args[1], modifiers=modifiers)
454
+
446
455
  @v_args(meta=True)
447
456
  def struct_type(self, meta: Meta, args) -> StructType:
448
457
  final: list[
449
- DataType | MapType | ArrayType | NumericType | StructType | Concept
458
+ DataType
459
+ | MapType
460
+ | ArrayType
461
+ | NumericType
462
+ | StructType
463
+ | StructComponent
464
+ | Concept
450
465
  ] = []
451
466
  for arg in args:
452
- new = self.environment.concepts.__getitem__( # type: ignore
453
- key=arg, line_no=meta.line
454
- )
455
- final.append(new)
467
+ if isinstance(arg, StructComponent):
468
+ final.append(arg)
469
+ else:
470
+ new = self.environment.concepts.__getitem__( # type: ignore
471
+ key=arg, line_no=meta.line
472
+ )
473
+ final.append(new)
456
474
 
457
475
  return StructType(
458
476
  fields=final,
459
- fields_map={x.name: x for x in final if isinstance(x, Concept)},
477
+ fields_map={
478
+ x.name: x for x in final if isinstance(x, (Concept, StructComponent))
479
+ },
460
480
  )
461
481
 
462
482
  def list_type(self, args) -> ArrayType:
@@ -2026,6 +2046,16 @@ class ParseToObjects(Transformer):
2026
2046
  def fbool(self, meta, args):
2027
2047
  return self.function_factory.create_function(args, FunctionType.BOOL, meta)
2028
2048
 
2049
+ @v_args(meta=True)
2050
+ def fmap_keys(self, meta, args):
2051
+ return self.function_factory.create_function(args, FunctionType.MAP_KEYS, meta)
2052
+
2053
+ @v_args(meta=True)
2054
+ def fmap_values(self, meta, args):
2055
+ return self.function_factory.create_function(
2056
+ args, FunctionType.MAP_VALUES, meta
2057
+ )
2058
+
2029
2059
  @v_args(meta=True)
2030
2060
  def farray_sum(self, meta, args):
2031
2061
  return self.function_factory.create_function(args, FunctionType.ARRAY_SUM, meta)
@@ -181,7 +181,8 @@
181
181
  //indexing into an expression is a function
182
182
  index_access: atom "[" int_lit "]"
183
183
  map_key_access: atom "[" string_lit "]"
184
- attr_access: atom "." string_lit
184
+ _ATTR_ACCESS.1 : "getattr("i
185
+ attr_access: ( _ATTR_ACCESS atom "," string_lit ")") | (atom "." string_lit)
185
186
 
186
187
  ?expr: comparison_root | between_root
187
188
 
@@ -309,6 +310,14 @@
309
310
  farray_transform: _ARRAY_TRANSFORM expr "," transform_lambda ")"
310
311
 
311
312
  _array_functions: farray_sum | farray_distinct | farray_sort | farray_transform | farray_to_string
313
+
314
+ //map_functions
315
+ _MAP_KEYS.1: "map_keys("i
316
+ fmap_keys: _MAP_KEYS expr ")"
317
+ _MAP_VALUES.1: "map_values("i
318
+ fmap_values: _MAP_VALUES expr ")"
319
+
320
+ _map_functions: fmap_keys | fmap_values
312
321
 
313
322
  // special aggregate
314
323
  _GROUP.1: "group("i
@@ -380,7 +389,7 @@
380
389
 
381
390
  _date_functions: fdate | fdate_add | fdate_sub | fdate_diff | fdatetime | ftimestamp | fsecond | fminute | fhour | fday | fday_of_week | fweek | fmonth | fquarter | fyear | fdate_part | fdate_trunc
382
391
 
383
- _static_functions: _string_functions | _math_functions | _array_functions
392
+ _static_functions: _string_functions | _math_functions | _array_functions | _map_functions
384
393
 
385
394
  custom_function: "@" IDENTIFIER "(" (expr ",")* expr ")"
386
395
 
@@ -424,8 +433,8 @@
424
433
  MODIFIER: /OPTIONAL|PARTIAL|NULLABLE/i
425
434
 
426
435
  SHORTHAND_MODIFIER: /~|\?/
427
-
428
- struct_type: "struct"i "<" ((data_type | IDENTIFIER) ",")* (data_type | IDENTIFIER) ","? ">"
436
+ struct_component: IDENTIFIER ":" data_type concept_nullable_modifier? metadata?
437
+ struct_type: "struct"i "<" ((struct_component | IDENTIFIER) ",")* (struct_component | IDENTIFIER) ","? ">"
429
438
 
430
439
  list_type: ("list"i | "array"i) "<" (data_type | IDENTIFIER) ">"
431
440
 
File without changes
File without changes
File without changes
File without changes