pytrilogy 0.0.3.8__tar.gz → 0.0.3.10__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 (132) hide show
  1. {pytrilogy-0.0.3.8/pytrilogy.egg-info → pytrilogy-0.0.3.10}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/pytrilogy.egg-info/SOURCES.txt +1 -0
  4. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_select.py +6 -5
  5. pytrilogy-0.0.3.10/tests/test_typing.py +42 -0
  6. pytrilogy-0.0.3.10/tests/test_user_functions.py +206 -0
  7. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/authoring/__init__.py +6 -0
  9. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/enums.py +1 -0
  10. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/functions.py +9 -0
  11. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/author.py +65 -32
  12. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/build.py +14 -1
  13. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/core.py +13 -0
  14. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/environment.py +2 -1
  15. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/statements/author.py +13 -0
  16. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/base.py +1 -0
  17. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/executor.py +5 -2
  18. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/common.py +32 -9
  19. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/parse_engine.py +24 -5
  20. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/trilogy.lark +12 -8
  21. pytrilogy-0.0.3.8/tests/test_user_functions.py +0 -17
  22. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/LICENSE.md +0 -0
  23. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/README.md +0 -0
  24. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/pyproject.toml +0 -0
  25. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/pytrilogy.egg-info/dependency_links.txt +0 -0
  26. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/pytrilogy.egg-info/entry_points.txt +0 -0
  27. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/pytrilogy.egg-info/requires.txt +0 -0
  28. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/pytrilogy.egg-info/top_level.txt +0 -0
  29. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/setup.cfg +0 -0
  30. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/setup.py +0 -0
  31. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_datatypes.py +0 -0
  32. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_declarations.py +0 -0
  33. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_derived_concepts.py +0 -0
  34. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_discovery_nodes.py +0 -0
  35. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_enums.py +0 -0
  36. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_environment.py +0 -0
  37. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_executor.py +0 -0
  38. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_functions.py +0 -0
  39. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_imports.py +0 -0
  40. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_metadata.py +0 -0
  41. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_models.py +0 -0
  42. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_multi_join_assignments.py +0 -0
  43. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_parse_engine.py +0 -0
  44. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_parsing.py +0 -0
  45. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_partial_handling.py +0 -0
  46. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_query_processing.py +0 -0
  47. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_show.py +0 -0
  48. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_statements.py +0 -0
  49. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_undefined_concept.py +0 -0
  50. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/tests/test_where_clause.py +0 -0
  51. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/compiler.py +0 -0
  52. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/constants.py +0 -0
  53. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/__init__.py +0 -0
  54. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/constants.py +0 -0
  55. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/env_processor.py +0 -0
  56. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/environment_helpers.py +0 -0
  57. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/ergonomics.py +0 -0
  58. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/exceptions.py +0 -0
  59. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/graph_models.py +0 -0
  60. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/internal.py +0 -0
  61. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/__init__.py +0 -0
  62. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/build_environment.py +0 -0
  63. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/datasource.py +0 -0
  64. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/models/execute.py +0 -0
  65. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/optimization.py +0 -0
  66. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/optimizations/__init__.py +0 -0
  67. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/optimizations/base_optimization.py +0 -0
  68. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/optimizations/inline_constant.py +0 -0
  69. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/optimizations/inline_datasource.py +0 -0
  70. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  71. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/__init__.py +0 -0
  72. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  73. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/graph_utils.py +0 -0
  74. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/__init__.py +0 -0
  75. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  76. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/common.py +0 -0
  77. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  78. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/group_node.py +0 -0
  79. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  80. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  81. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  82. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  83. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  84. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  85. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  86. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/select_node.py +0 -0
  87. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  88. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/union_node.py +0 -0
  89. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  90. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/node_generators/window_node.py +0 -0
  91. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/__init__.py +0 -0
  92. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/base_node.py +0 -0
  93. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/filter_node.py +0 -0
  94. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/group_node.py +0 -0
  95. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/merge_node.py +0 -0
  96. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  97. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/union_node.py +0 -0
  98. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  99. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/nodes/window_node.py +0 -0
  100. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/processing/utility.py +0 -0
  101. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/query_processor.py +0 -0
  102. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/statements/__init__.py +0 -0
  103. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/statements/build.py +0 -0
  104. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/statements/common.py +0 -0
  105. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/core/statements/execute.py +0 -0
  106. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/__init__.py +0 -0
  107. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/bigquery.py +0 -0
  108. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/common.py +0 -0
  109. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/config.py +0 -0
  110. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/dataframe.py +0 -0
  111. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/duckdb.py +0 -0
  112. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/enums.py +0 -0
  113. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/postgres.py +0 -0
  114. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/presto.py +0 -0
  115. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/snowflake.py +0 -0
  116. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/dialect/sql_server.py +0 -0
  117. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/engine.py +0 -0
  118. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/hooks/__init__.py +0 -0
  119. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/hooks/base_hook.py +0 -0
  120. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/hooks/graph_hook.py +0 -0
  121. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/hooks/query_debugger.py +0 -0
  122. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/metadata/__init__.py +0 -0
  123. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parser.py +0 -0
  124. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/__init__.py +0 -0
  125. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/config.py +0 -0
  126. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/exceptions.py +0 -0
  127. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/helpers.py +0 -0
  128. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/parsing/render.py +0 -0
  129. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/py.typed +0 -0
  130. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/scripts/__init__.py +0 -0
  131. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/scripts/trilogy.py +0 -0
  132. {pytrilogy-0.0.3.8 → pytrilogy-0.0.3.10}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.8
3
+ Version: 0.0.3.10
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.8
3
+ Version: 0.0.3.10
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -27,6 +27,7 @@ tests/test_query_processing.py
27
27
  tests/test_select.py
28
28
  tests/test_show.py
29
29
  tests/test_statements.py
30
+ tests/test_typing.py
30
31
  tests/test_undefined_concept.py
31
32
  tests/test_user_functions.py
32
33
  tests/test_where_clause.py
@@ -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,42 @@
1
+ from trilogy import Dialects
2
+
3
+
4
+ def test_typing():
5
+ env = Dialects.DUCK_DB.default_executor()
6
+ env.environment.parse(
7
+ """
8
+
9
+ type email string;
10
+
11
+ key customer_id int;
12
+ property customer_id.email string::email;
13
+
14
+ def is_valid_email(email) -> contains(email, '@');
15
+
16
+ datasource customers (
17
+ id:customer_id,
18
+ email: email
19
+ )
20
+ grain (customer_id)
21
+ query '''
22
+ select 1 as id, 'bright@gmail.com' as email
23
+ union all
24
+ select 2 as id, 'funky@hotmail.com' as email
25
+ ''';
26
+
27
+
28
+ """
29
+ )
30
+
31
+ results = env.execute_query(
32
+ """SELECT
33
+ customer_id,
34
+ @is_valid_email(email)->valid;"""
35
+ )
36
+
37
+ for row in results.fetchall():
38
+ assert row.valid is True
39
+
40
+ assert "email" in env.environment.data_types
41
+
42
+ assert env.environment.concepts["email"].datatype.traits == ["email"]
@@ -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.8"
7
+ __version__ = "0.0.3.10"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -38,6 +38,9 @@ from trilogy.core.models.environment import Environment
38
38
  from trilogy.core.statements.author import (
39
39
  ConceptDeclarationStatement,
40
40
  ConceptTransform,
41
+ MultiSelectStatement,
42
+ PersistStatement,
43
+ RawSQLStatement,
41
44
  SelectItem,
42
45
  SelectStatement,
43
46
  )
@@ -87,4 +90,7 @@ __all__ = [
87
90
  "DEFAULT_NAMESPACE",
88
91
  "arbitrary_to_concept",
89
92
  "arg_to_datatype",
93
+ "MultiSelectStatement",
94
+ "PersistStatement",
95
+ "RawSQLStatement",
90
96
  ]
@@ -172,6 +172,7 @@ class FunctionType(Enum):
172
172
  UPPER = "upper"
173
173
  SUBSTRING = "substring"
174
174
  STRPOS = "strpos"
175
+ CONTAINS = "contains"
175
176
 
176
177
  # Dates
177
178
  DATE = "date"
@@ -271,6 +271,15 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
271
271
  output_type=DataType.INTEGER,
272
272
  arg_count=2,
273
273
  ),
274
+ FunctionType.CONTAINS: FunctionConfig(
275
+ valid_inputs=[
276
+ {DataType.STRING},
277
+ {DataType.STRING},
278
+ ],
279
+ output_purpose=Purpose.PROPERTY,
280
+ output_type=DataType.BOOL,
281
+ arg_count=2,
282
+ ),
274
283
  FunctionType.SUBSTRING: FunctionConfig(
275
284
  valid_inputs=[{DataType.STRING}, {DataType.INTEGER}, {DataType.INTEGER}],
276
285
  output_purpose=Purpose.PROPERTY,
@@ -55,6 +55,7 @@ from trilogy.core.models.core import (
55
55
  MapWrapper,
56
56
  NumericType,
57
57
  StructType,
58
+ TraitDataType,
58
59
  TupleWrapper,
59
60
  arg_to_datatype,
60
61
  is_compatible_datatype,
@@ -101,9 +102,9 @@ class HasUUID(ABC):
101
102
 
102
103
  class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
103
104
  address: str
104
- datatype: DataType | ListType | StructType | MapType | NumericType = (
105
- DataType.UNKNOWN
106
- )
105
+ datatype: (
106
+ DataType | TraitDataType | ListType | StructType | MapType | NumericType
107
+ ) = DataType.UNKNOWN
107
108
  metadata: Optional["Metadata"] = None
108
109
 
109
110
  @property
@@ -547,6 +548,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
547
548
  MagicConstants,
548
549
  WindowItem,
549
550
  AggregateWrapper,
551
+ FilterItem,
550
552
  ]
551
553
  right: Union[
552
554
  int,
@@ -566,6 +568,7 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
566
568
  WindowItem,
567
569
  AggregateWrapper,
568
570
  TupleWrapper,
571
+ FilterItem,
569
572
  ]
570
573
  operator: ComparisonOperator
571
574
 
@@ -754,7 +757,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
754
757
  extra="forbid",
755
758
  )
756
759
  name: str
757
- datatype: DataType | ListType | StructType | MapType | NumericType
760
+ datatype: DataType | TraitDataType | ListType | StructType | MapType | NumericType
758
761
  purpose: Purpose
759
762
  derivation: Derivation = Derivation.ROOT
760
763
  granularity: Granularity = Granularity.MULTI_ROW
@@ -1215,9 +1218,9 @@ class UndefinedConceptFull(Concept, Mergeable, Namespaced):
1215
1218
  model_config = ConfigDict(arbitrary_types_allowed=True)
1216
1219
  name: str
1217
1220
  line_no: int | None = None
1218
- datatype: DataType | ListType | StructType | MapType | NumericType = (
1219
- DataType.UNKNOWN
1220
- )
1221
+ datatype: (
1222
+ DataType | TraitDataType | ListType | StructType | MapType | NumericType
1223
+ ) = DataType.UNKNOWN
1221
1224
  purpose: Purpose = Purpose.UNKNOWN
1222
1225
 
1223
1226
  @property
@@ -1249,7 +1252,7 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1249
1252
  def with_merge(
1250
1253
  self, source: Concept, target: Concept, modifiers: List[Modifier]
1251
1254
  ) -> "OrderItem":
1252
- return OrderItem(
1255
+ return OrderItem.model_construct(
1253
1256
  expr=(
1254
1257
  self.expr.with_merge(source, target, modifiers)
1255
1258
  if isinstance(self.expr, Mergeable)
@@ -1258,6 +1261,16 @@ class OrderItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1258
1261
  order=self.order,
1259
1262
  )
1260
1263
 
1264
+ def with_reference_replacement(self, source, target):
1265
+ return OrderItem.model_construct(
1266
+ expr=(
1267
+ self.expr.with_reference_replacement(source, target)
1268
+ if isinstance(self.expr, Mergeable)
1269
+ else self.expr
1270
+ ),
1271
+ order=self.order,
1272
+ )
1273
+
1261
1274
  @property
1262
1275
  def concept_arguments(self) -> Sequence[ConceptRef]:
1263
1276
  return get_concept_arguments(self.expr)
@@ -1320,6 +1333,17 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1320
1333
  )
1321
1334
  return output
1322
1335
 
1336
+ def with_reference_replacement(self, source, target):
1337
+ return WindowItem.model_construct(
1338
+ type=self.type,
1339
+ content=self.content.with_reference_replacement(source, target),
1340
+ over=[x.with_reference_replacement(source, target) for x in self.over],
1341
+ order_by=[
1342
+ x.with_reference_replacement(source, target) for x in self.order_by
1343
+ ],
1344
+ index=self.index,
1345
+ )
1346
+
1323
1347
  def with_namespace(self, namespace: str) -> "WindowItem":
1324
1348
  return WindowItem.model_construct(
1325
1349
  type=self.type,
@@ -1515,30 +1539,7 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1515
1539
  List[Set[DataType]],
1516
1540
  ]
1517
1541
  ] = None
1518
- arguments: Sequence[
1519
- Union[
1520
- ConceptRef,
1521
- AggregateWrapper,
1522
- Function,
1523
- Parenthetical,
1524
- CaseWhen,
1525
- CaseElse,
1526
- WindowItem,
1527
- int,
1528
- float,
1529
- str,
1530
- date,
1531
- datetime,
1532
- MapWrapper[Any, Any],
1533
- DataType,
1534
- ListType,
1535
- MapType,
1536
- NumericType,
1537
- DatePart,
1538
- list,
1539
- ListWrapper[Any],
1540
- ]
1541
- ]
1542
+ arguments: Sequence[FuncArgs]
1542
1543
 
1543
1544
  def __init__(self, **kwargs):
1544
1545
  super().__init__(**kwargs)
@@ -2153,6 +2154,11 @@ class ArgBinding(BaseModel):
2153
2154
  default: Expr | None = None
2154
2155
 
2155
2156
 
2157
+ class CustomType(BaseModel):
2158
+ name: str
2159
+ type: DataType
2160
+
2161
+
2156
2162
  Expr = (
2157
2163
  MagicConstants
2158
2164
  | bool
@@ -2164,6 +2170,7 @@ Expr = (
2164
2170
  | datetime
2165
2171
  | TupleWrapper
2166
2172
  | ListWrapper
2173
+ | MapWrapper
2167
2174
  | WindowItem
2168
2175
  | FilterItem
2169
2176
  | ConceptRef
@@ -2172,4 +2179,30 @@ Expr = (
2172
2179
  | Parenthetical
2173
2180
  | Function
2174
2181
  | AggregateWrapper
2182
+ | CaseWhen
2183
+ | CaseElse
2184
+ )
2185
+
2186
+ FuncArgs = (
2187
+ ConceptRef
2188
+ | AggregateWrapper
2189
+ | Function
2190
+ | Parenthetical
2191
+ | CaseWhen
2192
+ | CaseElse
2193
+ | WindowItem
2194
+ | FilterItem
2195
+ | int
2196
+ | float
2197
+ | str
2198
+ | date
2199
+ | datetime
2200
+ | MapWrapper[Any, Any]
2201
+ | DataType
2202
+ | ListType
2203
+ | MapType
2204
+ | NumericType
2205
+ | DatePart
2206
+ | list
2207
+ | ListWrapper[Any]
2175
2208
  )
@@ -51,6 +51,7 @@ from trilogy.core.models.author import (
51
51
  ConceptRef,
52
52
  Conditional,
53
53
  FilterItem,
54
+ FuncArgs,
54
55
  Function,
55
56
  Grain,
56
57
  HavingClause,
@@ -1498,10 +1499,22 @@ class Factory:
1498
1499
 
1499
1500
  @build.register
1500
1501
  def _(self, base: Function) -> BuildFunction:
1502
+ from trilogy.parsing.common import arbitrary_to_concept
1501
1503
 
1504
+ raw_args: list[Concept | FuncArgs] = []
1505
+ for arg in base.arguments:
1506
+ # to do proper discovery, we need to inject virtual intermediate ocncepts
1507
+ if isinstance(arg, (AggregateWrapper, FilterItem, WindowItem)):
1508
+ narg = arbitrary_to_concept(
1509
+ arg,
1510
+ environment=self.environment,
1511
+ )
1512
+ raw_args.append(narg)
1513
+ else:
1514
+ raw_args.append(arg)
1502
1515
  new = BuildFunction.model_construct(
1503
1516
  operator=base.operator,
1504
- arguments=[self.build(c) for c in base.arguments],
1517
+ arguments=[self.build(c) for c in raw_args],
1505
1518
  output_datatype=base.output_datatype,
1506
1519
  output_purpose=base.output_purpose,
1507
1520
  valid_inputs=base.valid_inputs,
@@ -97,6 +97,19 @@ class DataType(Enum):
97
97
  return self
98
98
 
99
99
 
100
+ class TraitDataType(BaseModel):
101
+ type: DataType
102
+ traits: list[str]
103
+
104
+ @property
105
+ def data_type(self):
106
+ return self.type
107
+
108
+ @property
109
+ def value(self):
110
+ return self.data_type.value
111
+
112
+
100
113
  class NumericType(BaseModel):
101
114
  precision: int = 20
102
115
  scale: int = 5
@@ -40,6 +40,7 @@ from trilogy.core.exceptions import (
40
40
  from trilogy.core.models.author import (
41
41
  Concept,
42
42
  ConceptRef,
43
+ CustomType,
43
44
  Function,
44
45
  SelectLineage,
45
46
  UndefinedConcept,
@@ -191,7 +192,7 @@ class Environment(BaseModel):
191
192
  EnvironmentDatasourceDict, PlainValidator(validate_datasources)
192
193
  ] = Field(default_factory=EnvironmentDatasourceDict)
193
194
  functions: Dict[str, Callable[..., Any]] = Field(default_factory=dict)
194
- data_types: Dict[str, DataType] = Field(default_factory=dict)
195
+ data_types: Dict[str, CustomType] = Field(default_factory=dict)
195
196
  named_statements: Dict[str, SelectLineage] = Field(default_factory=dict)
196
197
  imports: Dict[str, list[Import]] = Field(
197
198
  default_factory=lambda: defaultdict(list) # type: ignore
@@ -15,8 +15,11 @@ from trilogy.core.enums import (
15
15
  from trilogy.core.models.author import (
16
16
  AggregateWrapper,
17
17
  AlignClause,
18
+ ArgBinding,
18
19
  Concept,
19
20
  ConceptRef,
21
+ CustomType,
22
+ Expr,
20
23
  FilterItem,
21
24
  Function,
22
25
  Grain,
@@ -413,3 +416,13 @@ class ConceptDeclarationStatement(HasUUID, BaseModel):
413
416
 
414
417
  class ConceptDerivationStatement(BaseModel):
415
418
  concept: Concept
419
+
420
+
421
+ class TypeDeclaration(BaseModel):
422
+ type: CustomType
423
+
424
+
425
+ class FunctionDeclaration(BaseModel):
426
+ name: str
427
+ args: list[ArgBinding]
428
+ expr: Expr
@@ -184,6 +184,7 @@ FUNCTION_MAP = {
184
184
  FunctionType.LOWER: lambda x: f"LOWER({x[0]}) ",
185
185
  FunctionType.SUBSTRING: lambda x: f"SUBSTRING({x[0]},{x[1]},{x[2]})",
186
186
  FunctionType.STRPOS: lambda x: f"STRPOS({x[0]},{x[1]})",
187
+ FunctionType.CONTAINS: lambda x: f"CONTAINS({x[0]},{x[1]})",
187
188
  # FunctionType.NOT_LIKE: lambda x: f" CASE WHEN {x[0]} like {x[1]} THEN 0 ELSE 1 END",
188
189
  # date types
189
190
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
@@ -166,8 +166,11 @@ class Executor(object):
166
166
  )
167
167
 
168
168
  @execute_query.register
169
- def _(self, query: str) -> CursorResult:
170
- return self.execute_text(query)[-1]
169
+ def _(self, query: str) -> CursorResult | None:
170
+ results = self.execute_text(query)
171
+ if results:
172
+ return results[-1]
173
+ return None
171
174
 
172
175
  @execute_query.register
173
176
  def _(self, query: SelectStatement) -> CursorResult: