pytrilogy 0.0.3.100__tar.gz → 0.0.3.102__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 (159) hide show
  1. {pytrilogy-0.0.3.100/pytrilogy.egg-info → pytrilogy-0.0.3.102}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_parsing.py +1 -1
  5. pytrilogy-0.0.3.102/tests/test_validators.py +209 -0
  6. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/__init__.py +1 -1
  7. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/exceptions.py +1 -1
  8. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/functions.py +5 -2
  9. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/author.py +9 -0
  10. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/build.py +11 -4
  11. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/core.py +3 -0
  12. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/group_node.py +1 -0
  13. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/node_merge_node.py +0 -3
  14. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/validation/datasource.py +30 -6
  15. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/base.py +1 -1
  16. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/common.py +11 -1
  17. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/parse_engine.py +3 -0
  18. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/render.py +130 -29
  19. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/trilogy.lark +8 -3
  20. pytrilogy-0.0.3.102/trilogy/std/display.preql +9 -0
  21. pytrilogy-0.0.3.100/trilogy/std/display.preql +0 -6
  22. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/LICENSE.md +0 -0
  23. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/README.md +0 -0
  24. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/pyproject.toml +0 -0
  25. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/pytrilogy.egg-info/dependency_links.txt +0 -0
  26. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/pytrilogy.egg-info/entry_points.txt +0 -0
  27. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/pytrilogy.egg-info/requires.txt +0 -0
  28. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/pytrilogy.egg-info/top_level.txt +0 -0
  29. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/setup.cfg +0 -0
  30. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/setup.py +0 -0
  31. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_datatypes.py +0 -0
  32. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_declarations.py +0 -0
  33. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_derived_concepts.py +0 -0
  34. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_discovery_nodes.py +0 -0
  35. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_enums.py +0 -0
  36. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_environment.py +0 -0
  37. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_execute_models.py +0 -0
  38. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_executor.py +0 -0
  39. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_failure.py +0 -0
  40. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_functions.py +0 -0
  41. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_imports.py +0 -0
  42. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_metadata.py +0 -0
  43. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_models.py +0 -0
  44. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_multi_join_assignments.py +0 -0
  45. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_parse_engine.py +0 -0
  46. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_parsing_failures.py +0 -0
  47. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_partial_handling.py +0 -0
  48. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_query_processing.py +0 -0
  49. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_query_render.py +0 -0
  50. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_select.py +0 -0
  51. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_show.py +0 -0
  52. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_statements.py +0 -0
  53. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_typing.py +0 -0
  54. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_undefined_concept.py +0 -0
  55. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_user_functions.py +0 -0
  56. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/tests/test_where_clause.py +0 -0
  57. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/authoring/__init__.py +0 -0
  58. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/constants.py +0 -0
  59. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/__init__.py +0 -0
  60. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/constants.py +0 -0
  61. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/enums.py +0 -0
  62. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/env_processor.py +0 -0
  63. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/environment_helpers.py +0 -0
  64. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/ergonomics.py +0 -0
  65. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/graph_models.py +0 -0
  66. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/internal.py +0 -0
  67. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/__init__.py +0 -0
  68. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/build_environment.py +0 -0
  69. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/datasource.py +0 -0
  70. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/environment.py +0 -0
  71. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/models/execute.py +0 -0
  72. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/optimization.py +0 -0
  73. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/optimizations/__init__.py +0 -0
  74. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/optimizations/base_optimization.py +0 -0
  75. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/optimizations/inline_datasource.py +0 -0
  76. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  77. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/__init__.py +0 -0
  78. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  79. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/discovery_node_factory.py +0 -0
  80. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/discovery_utility.py +0 -0
  81. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/discovery_validation.py +0 -0
  82. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/graph_utils.py +0 -0
  83. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/__init__.py +0 -0
  84. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  85. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/common.py +0 -0
  86. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  87. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  88. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  89. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  90. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  91. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  92. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  93. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  94. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  95. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/select_node.py +0 -0
  96. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  97. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/union_node.py +0 -0
  98. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  99. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/node_generators/window_node.py +0 -0
  100. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/__init__.py +0 -0
  101. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/base_node.py +0 -0
  102. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/filter_node.py +0 -0
  103. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/group_node.py +0 -0
  104. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/merge_node.py +0 -0
  105. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  106. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  107. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/union_node.py +0 -0
  108. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  109. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/nodes/window_node.py +0 -0
  110. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/processing/utility.py +0 -0
  111. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/query_processor.py +0 -0
  112. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/statements/__init__.py +0 -0
  113. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/statements/author.py +0 -0
  114. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/statements/build.py +0 -0
  115. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/statements/common.py +0 -0
  116. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/statements/execute.py +0 -0
  117. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/utility.py +0 -0
  118. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/validation/__init__.py +0 -0
  119. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/validation/common.py +0 -0
  120. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/validation/concept.py +0 -0
  121. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/validation/environment.py +0 -0
  122. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/core/validation/fix.py +0 -0
  123. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/__init__.py +0 -0
  124. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/bigquery.py +0 -0
  125. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/common.py +0 -0
  126. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/config.py +0 -0
  127. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/dataframe.py +0 -0
  128. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/duckdb.py +0 -0
  129. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/enums.py +0 -0
  130. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/metadata.py +0 -0
  131. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/postgres.py +0 -0
  132. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/presto.py +0 -0
  133. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/snowflake.py +0 -0
  134. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/dialect/sql_server.py +0 -0
  135. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/engine.py +0 -0
  136. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/executor.py +0 -0
  137. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/hooks/__init__.py +0 -0
  138. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/hooks/base_hook.py +0 -0
  139. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/hooks/graph_hook.py +0 -0
  140. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/hooks/query_debugger.py +0 -0
  141. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/metadata/__init__.py +0 -0
  142. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parser.py +0 -0
  143. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/__init__.py +0 -0
  144. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/config.py +0 -0
  145. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/exceptions.py +0 -0
  146. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/parsing/helpers.py +0 -0
  147. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/py.typed +0 -0
  148. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/render.py +0 -0
  149. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/scripts/__init__.py +0 -0
  150. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/scripts/trilogy.py +0 -0
  151. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/__init__.py +0 -0
  152. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/date.preql +0 -0
  153. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/geography.preql +0 -0
  154. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/metric.preql +0 -0
  155. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/money.preql +0 -0
  156. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/net.preql +0 -0
  157. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/ranking.preql +0 -0
  158. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/std/report.preql +0 -0
  159. {pytrilogy-0.0.3.100 → pytrilogy-0.0.3.102}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.100
3
+ Version: 0.0.3.102
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.100
3
+ Version: 0.0.3.102
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -34,6 +34,7 @@ tests/test_statements.py
34
34
  tests/test_typing.py
35
35
  tests/test_undefined_concept.py
36
36
  tests/test_user_functions.py
37
+ tests/test_validators.py
37
38
  tests/test_where_clause.py
38
39
  trilogy/__init__.py
39
40
  trilogy/constants.py
@@ -552,7 +552,7 @@ const labels <- '';
552
552
 
553
553
  def test_struct_attr_access():
554
554
  text = """
555
- const labels <- struct(a->1, b->2, c->3);
555
+ const labels <- struct(1->a, 2->b, 3->c);
556
556
 
557
557
 
558
558
  select
@@ -0,0 +1,209 @@
1
+ from datetime import date, datetime
2
+ from decimal import Decimal
3
+
4
+ from trilogy.core.models.core import (
5
+ ArrayType,
6
+ DataType,
7
+ MapType,
8
+ NumericType,
9
+ StructType,
10
+ TraitDataType,
11
+ )
12
+ from trilogy.core.validation.datasource import type_check
13
+
14
+
15
+ def test_type_check():
16
+ # Basic string tests
17
+ assert type_check("hello", DataType.STRING)
18
+ assert not type_check(123, DataType.STRING)
19
+ assert type_check("", DataType.STRING) # empty string
20
+
21
+ # Integer tests
22
+ assert type_check(123, DataType.INTEGER)
23
+ assert type_check(0, DataType.INTEGER)
24
+ assert type_check(-123, DataType.INTEGER)
25
+ assert not type_check("123", DataType.INTEGER)
26
+ assert not type_check(123.0, DataType.INTEGER) # float should not match int
27
+
28
+ # BIGINT tests (same as INTEGER in implementation)
29
+ assert type_check(123, DataType.BIGINT)
30
+ assert type_check(2**63 - 1, DataType.BIGINT) # large integer
31
+ assert not type_check("123", DataType.BIGINT)
32
+ assert not type_check(123.0, DataType.BIGINT)
33
+
34
+ # Float tests
35
+ assert type_check(123.45, DataType.FLOAT)
36
+ assert type_check(123, DataType.FLOAT) # int should match float
37
+ assert type_check(0.0, DataType.FLOAT)
38
+ assert type_check(-123.45, DataType.FLOAT)
39
+ assert not type_check("123.45", DataType.FLOAT)
40
+
41
+ # Decimal support in float
42
+ decimal_val = Decimal("123.45")
43
+ assert type_check(decimal_val, DataType.FLOAT)
44
+
45
+ # NumericType tests
46
+ numeric_type = NumericType() # Assuming NumericType can be instantiated
47
+ assert type_check(123.45, numeric_type)
48
+ assert type_check(123, numeric_type)
49
+ assert type_check(decimal_val, numeric_type)
50
+ assert not type_check("123", numeric_type)
51
+
52
+ # NUMBER and NUMERIC tests (both handle int, float, Decimal)
53
+ assert type_check(123, DataType.NUMBER)
54
+ assert type_check(123.45, DataType.NUMBER)
55
+ assert type_check(decimal_val, DataType.NUMBER)
56
+ assert not type_check("123", DataType.NUMBER)
57
+
58
+ assert type_check(123, DataType.NUMERIC)
59
+ assert type_check(123.45, DataType.NUMERIC)
60
+ assert type_check(decimal_val, DataType.NUMERIC)
61
+ assert not type_check("123", DataType.NUMERIC)
62
+
63
+ # Boolean tests
64
+ assert type_check(True, DataType.BOOL)
65
+ assert type_check(False, DataType.BOOL)
66
+ assert not type_check(1, DataType.BOOL) # int should not match bool
67
+ assert not type_check(0, DataType.BOOL)
68
+ assert not type_check("true", DataType.BOOL)
69
+
70
+ # Date tests
71
+ test_date = date(2023, 12, 25)
72
+ assert type_check(test_date, DataType.DATE)
73
+ assert not type_check("2023-12-25", DataType.DATE)
74
+ assert not type_check(
75
+ datetime.now(), DataType.DATE
76
+ ) # datetime should not match date
77
+
78
+ # DateTime and Timestamp tests
79
+ test_datetime = datetime(2023, 12, 25, 15, 30, 45)
80
+ assert type_check(test_datetime, DataType.DATETIME)
81
+ assert type_check(test_datetime, DataType.TIMESTAMP)
82
+ assert not type_check("2023-12-25 15:30:45", DataType.DATETIME)
83
+ assert not type_check(
84
+ test_date, DataType.DATETIME
85
+ ) # date should not match datetime
86
+
87
+ # Unix seconds tests
88
+ assert type_check(1640995200, DataType.UNIX_SECONDS) # int timestamp
89
+ assert type_check(1640995200.123, DataType.UNIX_SECONDS) # float timestamp
90
+ assert not type_check("1640995200", DataType.UNIX_SECONDS)
91
+
92
+ # Date part tests
93
+ assert type_check("year", DataType.DATE_PART)
94
+ assert type_check("month", DataType.DATE_PART)
95
+ assert type_check("day", DataType.DATE_PART)
96
+ assert not type_check(2023, DataType.DATE_PART)
97
+
98
+ # Array tests
99
+ assert type_check([1, 2, 3], DataType.ARRAY)
100
+ assert type_check([], DataType.ARRAY) # empty array
101
+ assert type_check(["a", "b"], DataType.ARRAY)
102
+ assert not type_check("not a list", DataType.ARRAY)
103
+ assert not type_check({"key": "value"}, DataType.ARRAY)
104
+
105
+ # ArrayType tests
106
+ array_type = ArrayType(type=DataType.INTEGER)
107
+ assert type_check([1, 2, 3], array_type)
108
+ assert not type_check("not a list", array_type)
109
+
110
+ # Map tests
111
+ assert type_check({"key": "value"}, DataType.MAP)
112
+ assert type_check({}, DataType.MAP) # empty dict
113
+ assert type_check({"a": 1, "b": 2}, DataType.MAP)
114
+ assert not type_check([1, 2, 3], DataType.MAP)
115
+ assert not type_check("not a dict", DataType.MAP)
116
+
117
+ # MapType tests
118
+ map_type = MapType(key_type=DataType.STRING, value_type=DataType.STRING)
119
+ assert type_check({"key": "value"}, map_type)
120
+ assert not type_check([1, 2, 3], map_type)
121
+
122
+ # Struct tests
123
+ assert type_check({"field1": "value1"}, DataType.STRUCT)
124
+ assert type_check({}, DataType.STRUCT)
125
+ assert not type_check([1, 2, 3], DataType.STRUCT)
126
+
127
+ # StructType tests
128
+ struct_type = StructType(
129
+ fields=[DataType.STRING, DataType.STRING],
130
+ fields_map={"field1": DataType.STRING, "field2": DataType.STRING},
131
+ )
132
+ assert not type_check("not a dict", struct_type)
133
+
134
+ # NULL tests
135
+ assert type_check(None, DataType.NULL)
136
+ assert not type_check("", DataType.NULL)
137
+ assert not type_check(0, DataType.NULL)
138
+ assert not type_check(False, DataType.NULL)
139
+
140
+ # UNKNOWN tests (should accept anything)
141
+ assert type_check("anything", DataType.UNKNOWN)
142
+ assert type_check(123, DataType.UNKNOWN)
143
+ assert type_check(None, DataType.UNKNOWN)
144
+ assert type_check([1, 2, 3], DataType.UNKNOWN)
145
+ assert type_check({"key": "value"}, DataType.UNKNOWN)
146
+
147
+ # Nullable tests
148
+ assert type_check(None, DataType.STRING) # nullable by default
149
+ assert type_check(None, DataType.INTEGER)
150
+ assert type_check(None, DataType.FLOAT)
151
+ assert type_check(None, DataType.BOOL)
152
+
153
+ # Non-nullable tests
154
+ assert not type_check(None, DataType.STRING, nullable=False)
155
+ assert not type_check(None, DataType.INTEGER, nullable=False)
156
+ assert not type_check(None, DataType.FLOAT, nullable=False)
157
+ assert not type_check(None, DataType.BOOL, nullable=False)
158
+
159
+ # TraitDataType tests (recursive handling)
160
+ # Assuming TraitDataType wraps another DataType
161
+ trait_string_type = TraitDataType(type=DataType.STRING, traits=[])
162
+ assert type_check("hello", trait_string_type)
163
+ assert not type_check(123, trait_string_type)
164
+ assert type_check(None, trait_string_type) # nullable by default
165
+ assert not type_check(None, trait_string_type, nullable=False)
166
+
167
+ # Nested TraitDataType
168
+ nested_trait_type = TraitDataType(type=DataType.STRING, traits=[])
169
+ assert type_check("hello", nested_trait_type)
170
+ assert not type_check(123, nested_trait_type)
171
+
172
+ # Edge cases and invalid types
173
+ # Test with unsupported/custom types should return False
174
+ class CustomType:
175
+ pass
176
+
177
+ custom_obj = CustomType()
178
+ # These should all return False as they don't match any known type
179
+ assert not type_check(custom_obj, DataType.STRING)
180
+ assert not type_check(custom_obj, DataType.INTEGER)
181
+ assert not type_check(custom_obj, DataType.ARRAY)
182
+
183
+
184
+ def test_type_check_edge_cases():
185
+ """Additional edge case tests"""
186
+
187
+ # Test very large numbers
188
+ large_int = 2**100
189
+ assert type_check(large_int, DataType.INTEGER)
190
+ assert type_check(large_int, DataType.BIGINT)
191
+ assert type_check(large_int, DataType.FLOAT) # int should match float
192
+
193
+ # Test special float values
194
+ assert type_check(float("inf"), DataType.FLOAT)
195
+ assert type_check(float("-inf"), DataType.FLOAT)
196
+ # Note: NaN might need special handling depending on requirements
197
+ # assert type_check(float('nan'), DataType.FLOAT)
198
+
199
+ # Test empty collections
200
+ assert type_check([], DataType.ARRAY)
201
+ assert type_check({}, DataType.MAP)
202
+ assert type_check({}, DataType.STRUCT)
203
+
204
+ # Test nested collections
205
+ nested_list = [[1, 2], [3, 4]]
206
+ nested_dict = {"outer": {"inner": "value"}}
207
+ assert type_check(nested_list, DataType.ARRAY)
208
+ assert type_check(nested_dict, DataType.MAP)
209
+ assert type_check(nested_dict, DataType.STRUCT)
@@ -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.100"
7
+ __version__ = "0.0.3.102"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -69,7 +69,7 @@ class DatasourceColumnBindingData:
69
69
  actual_modifiers: List[Modifier]
70
70
 
71
71
  def format_failure(self):
72
- return f"Concept {self.address} value '{self.value}' with type {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
72
+ return f"Concept {self.address} value '{self.value}' with type {self.value_type} and {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
73
73
 
74
74
  def is_modifier_issue(self) -> bool:
75
75
  return len(self.value_modifiers) > 0 and any(
@@ -18,6 +18,7 @@ from trilogy.core.models.author import (
18
18
  AggregateWrapper,
19
19
  Concept,
20
20
  ConceptRef,
21
+ Conditional,
21
22
  Function,
22
23
  Parenthetical,
23
24
  UndefinedConcept,
@@ -129,8 +130,8 @@ def validate_case_output(
129
130
  def create_struct_output(
130
131
  args: list[Any],
131
132
  ) -> StructType:
132
- zipped = dict(zip(args[::2], args[1::2]))
133
- types = [arg_to_datatype(x) for x in args[1::2]]
133
+ zipped = dict(zip(args[1::2], args[::2]))
134
+ types = [arg_to_datatype(x) for x in args[::2]]
134
135
  return StructType(fields=types, fields_map=zipped)
135
136
 
136
137
 
@@ -997,6 +998,8 @@ def argument_to_purpose(arg) -> Purpose:
997
998
  return argument_to_purpose(arg.content)
998
999
  elif isinstance(arg, WindowItem):
999
1000
  return Purpose.PROPERTY
1001
+ elif isinstance(arg, Conditional):
1002
+ return Purpose.PROPERTY
1000
1003
  elif isinstance(arg, Concept):
1001
1004
  base = arg.purpose
1002
1005
  if (
@@ -259,6 +259,15 @@ class Parenthetical(
259
259
  )
260
260
  )
261
261
 
262
+ def with_reference_replacement(self, source, target):
263
+ return Parenthetical.model_construct(
264
+ content=(
265
+ self.content.with_reference_replacement(source, target)
266
+ if isinstance(self.content, Mergeable)
267
+ else self.content
268
+ )
269
+ )
270
+
262
271
  @property
263
272
  def concept_arguments(self) -> Sequence[ConceptRef]:
264
273
  base: List[ConceptRef] = []
@@ -1511,7 +1511,10 @@ def requires_concept_nesting(
1511
1511
  ) -> AggregateWrapper | WindowItem | FilterItem | Function | None:
1512
1512
  if isinstance(expr, (AggregateWrapper, WindowItem, FilterItem)):
1513
1513
  return expr
1514
- if isinstance(expr, Function) and expr.operator == FunctionType.GROUP:
1514
+ if isinstance(expr, Function) and expr.operator in (
1515
+ FunctionType.GROUP,
1516
+ FunctionType.PARENTHETICAL,
1517
+ ):
1515
1518
  # group by requires nesting
1516
1519
  return expr
1517
1520
  return None
@@ -1696,13 +1699,12 @@ class Factory:
1696
1699
  return self._build_case_when(base)
1697
1700
 
1698
1701
  def _build_case_when(self, base: CaseWhen) -> BuildCaseWhen:
1699
- comparison = base.comparison
1700
1702
  expr: Concept | FuncArgs = base.expr
1701
1703
  validation = requires_concept_nesting(expr)
1702
1704
  if validation:
1703
1705
  expr, _ = self.instantiate_concept(validation)
1704
1706
  return BuildCaseWhen(
1705
- comparison=self.build(comparison),
1707
+ comparison=self.build(base.comparison),
1706
1708
  expr=self.build(expr),
1707
1709
  )
1708
1710
 
@@ -2019,7 +2021,12 @@ class Factory:
2019
2021
  return self._build_parenthetical(base)
2020
2022
 
2021
2023
  def _build_parenthetical(self, base: Parenthetical) -> BuildParenthetical:
2022
- return BuildParenthetical(content=(self.build(base.content)))
2024
+ validate = requires_concept_nesting(base.content)
2025
+ if validate:
2026
+ content, _ = self.instantiate_concept(validate)
2027
+ return BuildParenthetical(content=self.build(content))
2028
+ else:
2029
+ return BuildParenthetical(content=self.build(base.content))
2023
2030
 
2024
2031
  @build.register
2025
2032
  def _(self, base: SelectLineage) -> BuildSelectLineage:
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from abc import ABC
4
4
  from collections import UserDict, UserList
5
5
  from datetime import date, datetime
6
+ from decimal import Decimal
6
7
  from enum import Enum
7
8
  from typing import (
8
9
  Any,
@@ -448,6 +449,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
448
449
  return DataType.STRING
449
450
  elif isinstance(arg, float):
450
451
  return DataType.FLOAT
452
+ elif isinstance(arg, Decimal):
453
+ return DataType.NUMERIC
451
454
  elif isinstance(arg, DataType):
452
455
  return arg
453
456
  elif isinstance(arg, NumericType):
@@ -28,6 +28,7 @@ def get_aggregate_grain(
28
28
  parent_concepts: List[BuildConcept] = unique(
29
29
  resolve_function_parent_concepts(concept, environment=environment), "address"
30
30
  )
31
+
31
32
  if (
32
33
  concept.grain
33
34
  and len(concept.grain.components) > 0
@@ -164,9 +164,6 @@ def reinject_common_join_keys_v2(
164
164
  reduced = BuildGrain.from_concepts(concrete_concepts).components
165
165
  existing_addresses = set()
166
166
  for concrete in concrete_concepts:
167
- logger.debug(
168
- f"looking at column {concrete.address} with pseudonyms {concrete.pseudonyms}"
169
- )
170
167
  cnode = concept_to_node(concrete.with_default_grain())
171
168
  if cnode in final.nodes:
172
169
  existing_addresses.add(concrete.address)
@@ -36,31 +36,51 @@ def type_check(
36
36
  ) -> bool:
37
37
  if input is None and nullable:
38
38
  return True
39
+
39
40
  target_type = expected_type
40
41
  while isinstance(target_type, TraitDataType):
41
42
  return type_check(input, target_type.data_type, nullable)
43
+
42
44
  if target_type == DataType.STRING:
43
45
  return isinstance(input, str)
44
46
  if target_type == DataType.INTEGER:
45
47
  return isinstance(input, int)
48
+ if target_type == DataType.BIGINT:
49
+ return isinstance(input, int) # or check for larger int if needed
46
50
  if target_type == DataType.FLOAT or isinstance(target_type, NumericType):
47
51
  return (
48
52
  isinstance(input, float)
49
53
  or isinstance(input, int)
50
54
  or isinstance(input, Decimal)
51
55
  )
56
+ if target_type == DataType.NUMBER:
57
+ return isinstance(input, (int, float, Decimal))
58
+ if target_type == DataType.NUMERIC:
59
+ return isinstance(input, (int, float, Decimal))
52
60
  if target_type == DataType.BOOL:
53
61
  return isinstance(input, bool)
54
62
  if target_type == DataType.DATE:
55
- return isinstance(input, date)
63
+ return isinstance(input, date) and not isinstance(input, datetime)
56
64
  if target_type == DataType.DATETIME:
57
65
  return isinstance(input, datetime)
66
+ if target_type == DataType.TIMESTAMP:
67
+ return isinstance(input, datetime) # or timestamp type if you have one
68
+ if target_type == DataType.UNIX_SECONDS:
69
+ return isinstance(input, (int, float)) # Unix timestamps are numeric
70
+ if target_type == DataType.DATE_PART:
71
+ return isinstance(
72
+ input, str
73
+ ) # assuming date parts are strings like "year", "month"
58
74
  if target_type == DataType.ARRAY or isinstance(target_type, ArrayType):
59
75
  return isinstance(input, list)
60
76
  if target_type == DataType.MAP or isinstance(target_type, MapType):
61
77
  return isinstance(input, dict)
62
78
  if target_type == DataType.STRUCT or isinstance(target_type, StructType):
63
79
  return isinstance(input, dict)
80
+ if target_type == DataType.NULL:
81
+ return input is None
82
+ if target_type == DataType.UNKNOWN:
83
+ return True
64
84
  return False
65
85
 
66
86
 
@@ -125,15 +145,19 @@ def validate_datasource(
125
145
  rval = row[actual_address]
126
146
  passed = type_check(rval, col.concept.datatype, col.is_nullable)
127
147
  if not passed:
148
+ value_type = (
149
+ arg_to_datatype(rval) if rval is not None else col.concept.datatype
150
+ )
151
+ traits = None
152
+ if isinstance(col.concept.datatype, TraitDataType):
153
+ traits = col.concept.datatype.traits
154
+ if traits and not isinstance(value_type, TraitDataType):
155
+ value_type = TraitDataType(type=value_type, traits=traits)
128
156
  failures.append(
129
157
  DatasourceColumnBindingData(
130
158
  address=col.concept.address,
131
159
  value=rval,
132
- value_type=(
133
- arg_to_datatype(rval)
134
- if rval is not None
135
- else col.concept.datatype
136
- ),
160
+ value_type=value_type,
137
161
  value_modifiers=[Modifier.NULLABLE] if rval is None else [],
138
162
  actual_type=col.concept.datatype,
139
163
  actual_modifiers=col.concept.modifiers,
@@ -163,7 +163,7 @@ def render_case(args):
163
163
 
164
164
 
165
165
  def struct_arg(args):
166
- return [f"{x[0]}: {x[1]}" for x in zip(args[::2], args[1::2])]
166
+ return [f"{x[1]}: {x[0]}" for x in zip(args[::2], args[1::2])]
167
167
 
168
168
 
169
169
  FUNCTION_MAP = {
@@ -249,11 +249,14 @@ def atom_is_relevant(
249
249
  return atom_is_relevant(atom.left, others, environment) or atom_is_relevant(
250
250
  atom.right, others, environment
251
251
  )
252
+ elif isinstance(atom, Parenthetical):
253
+ return atom_is_relevant(atom.content, others, environment)
252
254
  elif isinstance(atom, ConceptArgs):
253
255
  # use atom is relevant here to trigger the early exit behavior for concepts in set
254
256
  return any(
255
257
  [atom_is_relevant(x, others, environment) for x in atom.concept_arguments]
256
258
  )
259
+
257
260
  return False
258
261
 
259
262
 
@@ -294,12 +297,18 @@ def concept_is_relevant(
294
297
  if all([c in others for c in concept.grain.components]):
295
298
  return False
296
299
  if concept.derivation in (Derivation.BASIC,) and isinstance(
297
- concept.lineage, Function
300
+ concept.lineage, (Function, CaseWhen)
298
301
  ):
299
302
  relevant = False
300
303
  for arg in concept.lineage.arguments:
301
304
  relevant = atom_is_relevant(arg, others, environment) or relevant
305
+
302
306
  return relevant
307
+ if concept.derivation in (Derivation.BASIC,) and isinstance(
308
+ concept.lineage, Parenthetical
309
+ ):
310
+ return atom_is_relevant(concept.lineage.content, others, environment)
311
+
303
312
  if concept.granularity == Granularity.SINGLE_ROW:
304
313
  return False
305
314
  return True
@@ -346,6 +355,7 @@ def concepts_to_grain_concepts(
346
355
  if sub.address in seen:
347
356
  continue
348
357
  if not concept_is_relevant(sub, pconcepts, environment): # type: ignore
358
+
349
359
  continue
350
360
  seen.add(sub.address)
351
361
 
@@ -992,6 +992,9 @@ class ParseToObjects(Transformer):
992
992
  def order_by(self, args):
993
993
  return OrderBy(items=args[0])
994
994
 
995
+ def over_component(self, args):
996
+ return ConceptRef(address=args[0].value.lstrip(",").strip())
997
+
995
998
  def over_list(self, args):
996
999
  return [x for x in args]
997
1000