pytrilogy 0.0.3.47__tar.gz → 0.0.3.51__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 (141) hide show
  1. {pytrilogy-0.0.3.47/pytrilogy.egg-info → pytrilogy-0.0.3.51}/PKG-INFO +12 -15
  2. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/README.md +11 -14
  3. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51/pytrilogy.egg-info}/PKG-INFO +12 -15
  4. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/pytrilogy.egg-info/SOURCES.txt +0 -1
  5. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_functions.py +4 -0
  6. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_parse_engine.py +23 -1
  7. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/enums.py +3 -0
  9. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/functions.py +22 -0
  10. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/author.py +6 -2
  11. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/build.py +42 -8
  12. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/execute.py +6 -3
  13. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/optimization.py +0 -3
  14. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/optimizations/__init__.py +0 -2
  15. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/concept_strategies_v3.py +1 -1
  16. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/common.py +3 -4
  17. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/filter_node.py +142 -91
  18. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/group_node.py +7 -5
  19. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/base_node.py +4 -1
  20. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/query_processor.py +27 -12
  21. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/statements/author.py +0 -2
  22. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/base.py +28 -6
  23. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/bigquery.py +2 -0
  24. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/common.py +25 -14
  25. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/duckdb.py +2 -0
  26. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/executor.py +3 -0
  27. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/common.py +25 -16
  28. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/parse_engine.py +36 -8
  29. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/trilogy.lark +10 -4
  30. pytrilogy-0.0.3.47/trilogy/core/optimizations/inline_constant.py +0 -35
  31. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/LICENSE.md +0 -0
  32. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/pyproject.toml +0 -0
  33. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/pytrilogy.egg-info/dependency_links.txt +0 -0
  34. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/pytrilogy.egg-info/entry_points.txt +0 -0
  35. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/pytrilogy.egg-info/requires.txt +0 -0
  36. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/pytrilogy.egg-info/top_level.txt +0 -0
  37. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/setup.cfg +0 -0
  38. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/setup.py +0 -0
  39. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_datatypes.py +0 -0
  40. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_declarations.py +0 -0
  41. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_derived_concepts.py +0 -0
  42. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_discovery_nodes.py +0 -0
  43. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_enums.py +0 -0
  44. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_environment.py +0 -0
  45. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_executor.py +0 -0
  46. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_failure.py +0 -0
  47. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_imports.py +0 -0
  48. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_metadata.py +0 -0
  49. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_models.py +0 -0
  50. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_multi_join_assignments.py +0 -0
  51. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_parsing.py +0 -0
  52. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_parsing_failures.py +0 -0
  53. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_partial_handling.py +0 -0
  54. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_query_processing.py +0 -0
  55. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_query_render.py +0 -0
  56. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_select.py +0 -0
  57. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_show.py +0 -0
  58. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_statements.py +0 -0
  59. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_typing.py +0 -0
  60. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_undefined_concept.py +0 -0
  61. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_user_functions.py +0 -0
  62. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/tests/test_where_clause.py +0 -0
  63. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/authoring/__init__.py +0 -0
  64. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/compiler.py +0 -0
  65. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/constants.py +0 -0
  66. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/__init__.py +0 -0
  67. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/constants.py +0 -0
  68. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/env_processor.py +0 -0
  69. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/environment_helpers.py +0 -0
  70. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/ergonomics.py +0 -0
  71. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/exceptions.py +0 -0
  72. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/graph_models.py +0 -0
  73. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/internal.py +0 -0
  74. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/__init__.py +0 -0
  75. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/build_environment.py +0 -0
  76. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/core.py +0 -0
  77. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/datasource.py +0 -0
  78. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/models/environment.py +0 -0
  79. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/optimizations/base_optimization.py +0 -0
  80. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/optimizations/inline_datasource.py +0 -0
  81. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  82. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/__init__.py +0 -0
  83. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/graph_utils.py +0 -0
  84. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/__init__.py +0 -0
  85. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  86. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  87. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  88. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  89. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  90. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  91. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  92. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  93. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/select_node.py +0 -0
  94. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  95. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/union_node.py +0 -0
  96. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  97. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/node_generators/window_node.py +0 -0
  98. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/__init__.py +0 -0
  99. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/filter_node.py +0 -0
  100. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/group_node.py +0 -0
  101. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/merge_node.py +0 -0
  102. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  103. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/union_node.py +0 -0
  104. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  105. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/nodes/window_node.py +0 -0
  106. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/processing/utility.py +0 -0
  107. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/statements/__init__.py +0 -0
  108. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/statements/build.py +0 -0
  109. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/statements/common.py +0 -0
  110. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/core/statements/execute.py +0 -0
  111. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/__init__.py +0 -0
  112. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/config.py +0 -0
  113. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/dataframe.py +0 -0
  114. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/enums.py +0 -0
  115. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/postgres.py +0 -0
  116. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/presto.py +0 -0
  117. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/snowflake.py +0 -0
  118. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/dialect/sql_server.py +0 -0
  119. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/engine.py +0 -0
  120. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/hooks/__init__.py +0 -0
  121. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/hooks/base_hook.py +0 -0
  122. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/hooks/graph_hook.py +0 -0
  123. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/hooks/query_debugger.py +0 -0
  124. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/metadata/__init__.py +0 -0
  125. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parser.py +0 -0
  126. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/__init__.py +0 -0
  127. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/config.py +0 -0
  128. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/exceptions.py +0 -0
  129. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/helpers.py +0 -0
  130. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/parsing/render.py +0 -0
  131. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/py.typed +0 -0
  132. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/render.py +0 -0
  133. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/scripts/__init__.py +0 -0
  134. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/scripts/trilogy.py +0 -0
  135. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/std/__init__.py +0 -0
  136. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/std/date.preql +0 -0
  137. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/std/display.preql +0 -0
  138. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/std/geography.preql +0 -0
  139. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/std/money.preql +0 -0
  140. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/std/report.preql +0 -0
  141. {pytrilogy-0.0.3.47 → pytrilogy-0.0.3.51}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.47
3
+ Version: 0.0.3.51
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -53,7 +53,7 @@ Installation: `pip install pytrilogy`
53
53
 
54
54
  You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
55
55
 
56
- Trilogy:
56
+ Trilogy looks like SQL:
57
57
  ```sql
58
58
  WHERE
59
59
  name like '%lvis%'
@@ -65,7 +65,7 @@ ORDER BY
65
65
  LIMIT 10;
66
66
  ```
67
67
  ## Goals
68
- vs SQL, the goals are:
68
+ And aims to:
69
69
 
70
70
  Preserve:
71
71
  - Correctness
@@ -85,6 +85,7 @@ Maintain:
85
85
  Save the following code in a file named `hello.preql`
86
86
 
87
87
  ```python
88
+ # semantic model is abstract from data
88
89
  key sentence_id int;
89
90
  property sentence_id.word_one string; # comments after a definition
90
91
  property sentence_id.word_two string; # are syntactic sugar for adding
@@ -92,7 +93,8 @@ property sentence_id.word_three string; # a description to it
92
93
 
93
94
  # comments in other places are just comments
94
95
 
95
- # define our datasources as queries in duckdb
96
+ # define our datasource to bind the model to data
97
+ # testing using query fixtures is a common pattern
96
98
  datasource word_one(
97
99
  sentence: sentence_id,
98
100
  word:word_one
@@ -126,25 +128,20 @@ union all
126
128
  select 2 as sentence, '!'
127
129
  ''';
128
130
 
131
+ def concat_with_space(x,y) -> x || ' ' || y;
132
+
129
133
  # an actual select statement
130
134
  # joins are automatically resolved between the 3 sources
131
135
  with sentences as
132
- select sentence_id, word_one || ' ' || word_two || word_three as text;
136
+ select sentence_id, @concat_with_space(word_one, word_two) || word_three as text;
133
137
 
134
- SELECT
135
- --sentences.sentence_id,
136
- sentences.text
137
138
  WHERE
138
- sentences.sentence_id = 1
139
- ;
140
-
139
+ sentences.sentence_id in (1,2)
141
140
  SELECT
142
- --sentences.sentence_id,
143
141
  sentences.text
144
- WHERE
145
- sentences.sentence_id = 2
146
142
  ;
147
- # semicolon termination for all statements
143
+
144
+
148
145
 
149
146
  ```
150
147
 
@@ -15,7 +15,7 @@ Installation: `pip install pytrilogy`
15
15
 
16
16
  You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
17
17
 
18
- Trilogy:
18
+ Trilogy looks like SQL:
19
19
  ```sql
20
20
  WHERE
21
21
  name like '%lvis%'
@@ -27,7 +27,7 @@ ORDER BY
27
27
  LIMIT 10;
28
28
  ```
29
29
  ## Goals
30
- vs SQL, the goals are:
30
+ And aims to:
31
31
 
32
32
  Preserve:
33
33
  - Correctness
@@ -47,6 +47,7 @@ Maintain:
47
47
  Save the following code in a file named `hello.preql`
48
48
 
49
49
  ```python
50
+ # semantic model is abstract from data
50
51
  key sentence_id int;
51
52
  property sentence_id.word_one string; # comments after a definition
52
53
  property sentence_id.word_two string; # are syntactic sugar for adding
@@ -54,7 +55,8 @@ property sentence_id.word_three string; # a description to it
54
55
 
55
56
  # comments in other places are just comments
56
57
 
57
- # define our datasources as queries in duckdb
58
+ # define our datasource to bind the model to data
59
+ # testing using query fixtures is a common pattern
58
60
  datasource word_one(
59
61
  sentence: sentence_id,
60
62
  word:word_one
@@ -88,25 +90,20 @@ union all
88
90
  select 2 as sentence, '!'
89
91
  ''';
90
92
 
93
+ def concat_with_space(x,y) -> x || ' ' || y;
94
+
91
95
  # an actual select statement
92
96
  # joins are automatically resolved between the 3 sources
93
97
  with sentences as
94
- select sentence_id, word_one || ' ' || word_two || word_three as text;
98
+ select sentence_id, @concat_with_space(word_one, word_two) || word_three as text;
95
99
 
96
- SELECT
97
- --sentences.sentence_id,
98
- sentences.text
99
100
  WHERE
100
- sentences.sentence_id = 1
101
- ;
102
-
101
+ sentences.sentence_id in (1,2)
103
102
  SELECT
104
- --sentences.sentence_id,
105
103
  sentences.text
106
- WHERE
107
- sentences.sentence_id = 2
108
104
  ;
109
- # semicolon termination for all statements
105
+
106
+
110
107
 
111
108
  ```
112
109
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.47
3
+ Version: 0.0.3.51
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -53,7 +53,7 @@ Installation: `pip install pytrilogy`
53
53
 
54
54
  You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
55
55
 
56
- Trilogy:
56
+ Trilogy looks like SQL:
57
57
  ```sql
58
58
  WHERE
59
59
  name like '%lvis%'
@@ -65,7 +65,7 @@ ORDER BY
65
65
  LIMIT 10;
66
66
  ```
67
67
  ## Goals
68
- vs SQL, the goals are:
68
+ And aims to:
69
69
 
70
70
  Preserve:
71
71
  - Correctness
@@ -85,6 +85,7 @@ Maintain:
85
85
  Save the following code in a file named `hello.preql`
86
86
 
87
87
  ```python
88
+ # semantic model is abstract from data
88
89
  key sentence_id int;
89
90
  property sentence_id.word_one string; # comments after a definition
90
91
  property sentence_id.word_two string; # are syntactic sugar for adding
@@ -92,7 +93,8 @@ property sentence_id.word_three string; # a description to it
92
93
 
93
94
  # comments in other places are just comments
94
95
 
95
- # define our datasources as queries in duckdb
96
+ # define our datasource to bind the model to data
97
+ # testing using query fixtures is a common pattern
96
98
  datasource word_one(
97
99
  sentence: sentence_id,
98
100
  word:word_one
@@ -126,25 +128,20 @@ union all
126
128
  select 2 as sentence, '!'
127
129
  ''';
128
130
 
131
+ def concat_with_space(x,y) -> x || ' ' || y;
132
+
129
133
  # an actual select statement
130
134
  # joins are automatically resolved between the 3 sources
131
135
  with sentences as
132
- select sentence_id, word_one || ' ' || word_two || word_three as text;
136
+ select sentence_id, @concat_with_space(word_one, word_two) || word_three as text;
133
137
 
134
- SELECT
135
- --sentences.sentence_id,
136
- sentences.text
137
138
  WHERE
138
- sentences.sentence_id = 1
139
- ;
140
-
139
+ sentences.sentence_id in (1,2)
141
140
  SELECT
142
- --sentences.sentence_id,
143
141
  sentences.text
144
- WHERE
145
- sentences.sentence_id = 2
146
142
  ;
147
- # semicolon termination for all statements
143
+
144
+
148
145
 
149
146
  ```
150
147
 
@@ -66,7 +66,6 @@ trilogy/core/models/environment.py
66
66
  trilogy/core/models/execute.py
67
67
  trilogy/core/optimizations/__init__.py
68
68
  trilogy/core/optimizations/base_optimization.py
69
- trilogy/core/optimizations/inline_constant.py
70
69
  trilogy/core/optimizations/inline_datasource.py
71
70
  trilogy/core/optimizations/predicate_pushdown.py
72
71
  trilogy/core/processing/__init__.py
@@ -214,6 +214,8 @@ def test_math_functions(test_environment):
214
214
  property order_id.order_nested <- revenue * 2/2;
215
215
  property order_id.rounded <- round(revenue + 2.01,2);
216
216
  property order_id.rounded_default <- round(revenue + 2.01);
217
+ property order_id.floor <- floor(revenue + 2.01);
218
+ property order_id.ceil <- ceil(revenue + 2.01);
217
219
  constant random <- random(1);
218
220
  select
219
221
  order_id,
@@ -224,6 +226,8 @@ def test_math_functions(test_environment):
224
226
  order_add,
225
227
  rounded,
226
228
  rounded_default,
229
+ floor,
230
+ ceil,
227
231
  random,
228
232
  ;"""
229
233
  env, parsed = parse(declarations, environment=test_environment)
@@ -2,7 +2,12 @@ from pytest import raises
2
2
 
3
3
  from trilogy.core.exceptions import UndefinedConceptException
4
4
  from trilogy.core.models.environment import Environment
5
- from trilogy.parsing.parse_engine import PARSER, ParseToObjects, unpack_visit_error
5
+ from trilogy.parsing.parse_engine import (
6
+ PARSER,
7
+ InvalidSyntaxException,
8
+ ParseToObjects,
9
+ unpack_visit_error,
10
+ )
6
11
 
7
12
  TEXT = """
8
13
  const a <- 1;
@@ -29,3 +34,20 @@ def test_parser():
29
34
  with raises(UndefinedConceptException):
30
35
  unpack_visit_error(e)
31
36
  assert failed
37
+
38
+
39
+ TEXT2 = """
40
+ const a <- 1;
41
+
42
+ select
43
+ a,
44
+ FROM a
45
+ ;
46
+ """
47
+
48
+
49
+ def test_from_error():
50
+ env = Environment()
51
+
52
+ with raises(InvalidSyntaxException):
53
+ env.parse(TEXT2)
@@ -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.47"
7
+ __version__ = "0.0.3.51"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -131,6 +131,7 @@ class FunctionType(Enum):
131
131
  CONSTANT = "constant"
132
132
  COALESCE = "coalesce"
133
133
  IS_NULL = "isnull"
134
+ NULLIF = "nullif"
134
135
  BOOL = "bool"
135
136
 
136
137
  # COMPLEX
@@ -156,6 +157,8 @@ class FunctionType(Enum):
156
157
  ABS = "abs"
157
158
  SQRT = "sqrt"
158
159
  RANDOM = "random"
160
+ FLOOR = "floor"
161
+ CEIL = "ceil"
159
162
 
160
163
  # Aggregates
161
164
  ## group is not a real aggregate - it just means group by this + some other set of fields
@@ -279,6 +279,12 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
279
279
  output_purpose=Purpose.PROPERTY,
280
280
  arg_count=1,
281
281
  ),
282
+ FunctionType.NULLIF: FunctionConfig(
283
+ valid_inputs={*DataType},
284
+ output_purpose=Purpose.PROPERTY,
285
+ output_type_function=lambda args: get_output_type_at_index(args, 0),
286
+ arg_count=2,
287
+ ),
282
288
  FunctionType.COALESCE: FunctionConfig(
283
289
  valid_inputs={*DataType},
284
290
  output_purpose=Purpose.PROPERTY,
@@ -637,6 +643,22 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
637
643
  output_type_function=lambda args: get_output_type_at_index(args, 0),
638
644
  arg_count=2,
639
645
  ),
646
+ FunctionType.FLOOR: FunctionConfig(
647
+ valid_inputs=[
648
+ {DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
649
+ ],
650
+ output_purpose=Purpose.PROPERTY,
651
+ output_type=DataType.INTEGER,
652
+ arg_count=1,
653
+ ),
654
+ FunctionType.CEIL: FunctionConfig(
655
+ valid_inputs=[
656
+ {DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
657
+ ],
658
+ output_purpose=Purpose.PROPERTY,
659
+ output_type=DataType.INTEGER,
660
+ arg_count=1,
661
+ ),
640
662
  FunctionType.CUSTOM: FunctionConfig(
641
663
  output_purpose=Purpose.PROPERTY,
642
664
  arg_count=InfiniteFunctionArgs,
@@ -1945,11 +1945,15 @@ class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
1945
1945
 
1946
1946
  @property
1947
1947
  def output_datatype(self):
1948
- return self.content.datatype
1948
+ return arg_to_datatype(self.content)
1949
1949
 
1950
1950
  @property
1951
1951
  def concept_arguments(self):
1952
- return [self.content] + self.where.concept_arguments
1952
+ if isinstance(self.content, ConceptRef):
1953
+ return [self.content] + self.where.concept_arguments
1954
+ elif isinstance(self.content, ConceptArgs):
1955
+ return self.content.concept_arguments + self.where.concept_arguments
1956
+ return self.where.concept_arguments
1953
1957
 
1954
1958
 
1955
1959
  class RowsetLineage(Namespaced, Mergeable, BaseModel):
@@ -247,6 +247,17 @@ def get_concept_arguments(expr) -> List["BuildConcept"]:
247
247
  return output
248
248
 
249
249
 
250
+ class BuildParamaterizedConceptReference(BaseModel):
251
+ concept: BuildConcept
252
+
253
+ def __str__(self):
254
+ return f":{self.concept.address}"
255
+
256
+ @property
257
+ def safe_address(self) -> str:
258
+ return self.concept.safe_address
259
+
260
+
250
261
  class BuildGrain(BaseModel):
251
262
  components: set[str] = Field(default_factory=set)
252
263
  where_clause: Optional[BuildWhereClause] = None
@@ -1162,7 +1173,7 @@ class BuildAggregateWrapper(BuildConceptArgs, DataTyped, BaseModel):
1162
1173
 
1163
1174
 
1164
1175
  class BuildFilterItem(BuildConceptArgs, BaseModel):
1165
- content: BuildConcept
1176
+ content: "BuildExpr"
1166
1177
  where: BuildWhereClause
1167
1178
 
1168
1179
  def __str__(self):
@@ -1170,15 +1181,27 @@ class BuildFilterItem(BuildConceptArgs, BaseModel):
1170
1181
 
1171
1182
  @property
1172
1183
  def output_datatype(self):
1173
- return self.content.datatype
1184
+ return arg_to_datatype(self.content)
1174
1185
 
1175
1186
  @property
1176
1187
  def output_purpose(self):
1177
1188
  return self.content.purpose
1178
1189
 
1190
+ @property
1191
+ def content_concept_arguments(self):
1192
+ if isinstance(self.content, BuildConcept):
1193
+ return [self.content]
1194
+ elif isinstance(self.content, BuildConceptArgs):
1195
+ return self.content.concept_arguments
1196
+ return []
1197
+
1179
1198
  @property
1180
1199
  def concept_arguments(self):
1181
- return [self.content] + self.where.concept_arguments
1200
+ if isinstance(self.content, BuildConcept):
1201
+ return [self.content] + self.where.concept_arguments
1202
+ elif isinstance(self.content, BuildConceptArgs):
1203
+ return self.content.concept_arguments + self.where.concept_arguments
1204
+ return self.where.concept_arguments
1182
1205
 
1183
1206
 
1184
1207
  class BuildRowsetLineage(BuildConceptArgs, BaseModel):
@@ -1578,7 +1601,7 @@ class Factory:
1578
1601
 
1579
1602
  new = BuildFunction.model_construct(
1580
1603
  operator=base.operator,
1581
- arguments=[self.build(c) for c in raw_args],
1604
+ arguments=[self.handle_constant(self.build(c)) for c in raw_args],
1582
1605
  output_datatype=base.output_datatype,
1583
1606
  output_purpose=base.output_purpose,
1584
1607
  valid_inputs=base.valid_inputs,
@@ -1615,6 +1638,8 @@ class Factory:
1615
1638
 
1616
1639
  @build.register
1617
1640
  def _(self, base: Concept) -> BuildConcept:
1641
+
1642
+ # TODO: if we are using parameters, wrap it in a new model and use that in rendering
1618
1643
  if base.address in self.local_concepts:
1619
1644
  return self.local_concepts[base.address]
1620
1645
  new_lineage, final_grain, _ = base.get_select_grain_and_keys(
@@ -1738,8 +1763,8 @@ class Factory:
1738
1763
  @build.register
1739
1764
  def _(self, base: Conditional) -> BuildConditional:
1740
1765
  return BuildConditional.model_construct(
1741
- left=(self.build(base.left)),
1742
- right=(self.build(base.right)),
1766
+ left=self.handle_constant(self.build(base.left)),
1767
+ right=self.handle_constant(self.build(base.right)),
1743
1768
  operator=base.operator,
1744
1769
  )
1745
1770
 
@@ -1767,8 +1792,8 @@ class Factory:
1767
1792
  right_c, _ = self.instantiate_concept(right)
1768
1793
  right = right_c # type: ignore
1769
1794
  return BuildComparison.model_construct(
1770
- left=(self.build(left)),
1771
- right=(self.build(right)),
1795
+ left=self.handle_constant(self.build(left)),
1796
+ right=self.handle_constant(self.build(right)),
1772
1797
  operator=base.operator,
1773
1798
  )
1774
1799
 
@@ -2015,3 +2040,12 @@ class Factory:
2015
2040
  factory.build(base.non_partial_for) if base.non_partial_for else None
2016
2041
  ),
2017
2042
  )
2043
+
2044
+ def handle_constant(self, base):
2045
+ if (
2046
+ isinstance(base, BuildConcept)
2047
+ and isinstance(base.lineage, BuildFunction)
2048
+ and base.lineage.operator == FunctionType.CONSTANT
2049
+ ):
2050
+ return BuildParamaterizedConceptReference(concept=base)
2051
+ return base
@@ -24,6 +24,7 @@ from trilogy.core.models.build import (
24
24
  BuildFunction,
25
25
  BuildGrain,
26
26
  BuildOrderBy,
27
+ BuildParamaterizedConceptReference,
27
28
  BuildParenthetical,
28
29
  BuildRowsetItem,
29
30
  LooseBuildConceptList,
@@ -447,7 +448,7 @@ class CTEConceptPair(ConceptPair):
447
448
 
448
449
 
449
450
  class InstantiatedUnnestJoin(BaseModel):
450
- concept_to_unnest: BuildConcept
451
+ object_to_unnest: BuildConcept | BuildParamaterizedConceptReference | BuildFunction
451
452
  alias: str = "unnest"
452
453
 
453
454
 
@@ -694,7 +695,7 @@ class QueryDatasource(BaseModel):
694
695
  "can only merge two datasources if the force_group flag is the same"
695
696
  )
696
697
  logger.debug(
697
- f"{LOGGER_PREFIX} merging {self.name} with"
698
+ f"[Query Datasource] merging {self.name} with"
698
699
  f" {[c.address for c in self.output_concepts]} concepts and"
699
700
  f" {other.name} with {[c.address for c in other.output_concepts]} concepts"
700
701
  )
@@ -758,7 +759,9 @@ class QueryDatasource(BaseModel):
758
759
  hidden_concepts=hidden,
759
760
  ordering=self.ordering,
760
761
  )
761
-
762
+ logger.debug(
763
+ f"[Query Datasource] merged with {[c.address for c in qds.output_concepts]} concepts"
764
+ )
762
765
  return qds
763
766
 
764
767
  @property
@@ -5,7 +5,6 @@ from trilogy.core.models.build import (
5
5
  )
6
6
  from trilogy.core.models.execute import CTE, UnionCTE
7
7
  from trilogy.core.optimizations import (
8
- InlineConstant,
9
8
  InlineDatasource,
10
9
  OptimizationRule,
11
10
  PredicatePushdown,
@@ -206,8 +205,6 @@ def optimize_ctes(
206
205
  REGISTERED_RULES.append(PredicatePushdown())
207
206
  if CONFIG.optimizations.predicate_pushdown:
208
207
  REGISTERED_RULES.append(PredicatePushdownRemove())
209
- if CONFIG.optimizations.constant_inlining:
210
- REGISTERED_RULES.append(InlineConstant())
211
208
  for rule in REGISTERED_RULES:
212
209
  loops = 0
213
210
  complete = False
@@ -1,11 +1,9 @@
1
1
  from .base_optimization import OptimizationRule
2
- from .inline_constant import InlineConstant
3
2
  from .inline_datasource import InlineDatasource
4
3
  from .predicate_pushdown import PredicatePushdown, PredicatePushdownRemove
5
4
 
6
5
  __all__ = [
7
6
  "OptimizationRule",
8
- "InlineConstant",
9
7
  "InlineDatasource",
10
8
  "PredicatePushdown",
11
9
  "PredicatePushdownRemove",
@@ -319,7 +319,7 @@ def generate_node(
319
319
  ]
320
320
 
321
321
  logger.info(
322
- f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating aggregate node with {[x.address for x in agg_optional]}"
322
+ f"{depth_to_prefix(depth)}{LOGGER_PREFIX} for {concept.address}, generating aggregate node with {[x for x in agg_optional]}"
323
323
  )
324
324
  return gen_group_node(
325
325
  concept,
@@ -67,14 +67,14 @@ def resolve_condition_parent_concepts(
67
67
  def resolve_filter_parent_concepts(
68
68
  concept: BuildConcept,
69
69
  environment: BuildEnvironment,
70
- ) -> Tuple[BuildConcept, List[BuildConcept], List[Tuple[BuildConcept, ...]]]:
70
+ ) -> Tuple[List[BuildConcept], List[Tuple[BuildConcept, ...]]]:
71
71
  if not isinstance(concept.lineage, (BuildFilterItem,)):
72
72
  raise ValueError(
73
73
  f"Concept {concept} lineage is not filter item, is {type(concept.lineage)}"
74
74
  )
75
75
  direct_parent = concept.lineage.content
76
76
  base_existence = []
77
- base_rows = [direct_parent]
77
+ base_rows = [direct_parent] if isinstance(direct_parent, BuildConcept) else []
78
78
  condition_rows, condition_existence = resolve_condition_parent_concepts(
79
79
  concept.lineage.where
80
80
  )
@@ -90,11 +90,10 @@ def resolve_filter_parent_concepts(
90
90
 
91
91
  if concept.lineage.where.existence_arguments:
92
92
  return (
93
- concept.lineage.content,
94
93
  unique(base_rows, "address"),
95
94
  base_existence,
96
95
  )
97
- return concept.lineage.content, unique(base_rows, "address"), []
96
+ return unique(base_rows, "address"), []
98
97
 
99
98
 
100
99
  def gen_property_enrichment_node(