pytrilogy 0.0.3.68__tar.gz → 0.0.3.70__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 (149) hide show
  1. {pytrilogy-0.0.3.68/pytrilogy.egg-info → pytrilogy-0.0.3.70}/PKG-INFO +34 -19
  2. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/README.md +33 -18
  3. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70/pytrilogy.egg-info}/PKG-INFO +34 -19
  4. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/functions.py +2 -2
  6. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/author.py +1 -3
  7. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/build.py +20 -7
  8. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/discovery_node_factory.py +1 -0
  9. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/group_node.py +22 -3
  10. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/select_merge_node.py +15 -8
  11. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/query_processor.py +1 -2
  12. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/base.py +39 -6
  13. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/bigquery.py +19 -2
  14. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/duckdb.py +17 -0
  15. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/common.py +3 -2
  16. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/parse_engine.py +5 -1
  17. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/trilogy.lark +1 -1
  18. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/LICENSE.md +0 -0
  19. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/pyproject.toml +0 -0
  20. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/pytrilogy.egg-info/SOURCES.txt +0 -0
  21. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/pytrilogy.egg-info/dependency_links.txt +0 -0
  22. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/pytrilogy.egg-info/entry_points.txt +0 -0
  23. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/pytrilogy.egg-info/requires.txt +0 -0
  24. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/pytrilogy.egg-info/top_level.txt +0 -0
  25. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/setup.cfg +0 -0
  26. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/setup.py +0 -0
  27. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_datatypes.py +0 -0
  28. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_declarations.py +0 -0
  29. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_derived_concepts.py +0 -0
  30. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_discovery_nodes.py +0 -0
  31. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_enums.py +0 -0
  32. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_environment.py +0 -0
  33. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_executor.py +0 -0
  34. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_failure.py +0 -0
  35. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_functions.py +0 -0
  36. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_imports.py +0 -0
  37. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_metadata.py +0 -0
  38. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_models.py +0 -0
  39. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_multi_join_assignments.py +0 -0
  40. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_parse_engine.py +0 -0
  41. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_parsing.py +0 -0
  42. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_parsing_failures.py +0 -0
  43. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_partial_handling.py +0 -0
  44. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_query_processing.py +0 -0
  45. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_query_render.py +0 -0
  46. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_select.py +0 -0
  47. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_show.py +0 -0
  48. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_statements.py +0 -0
  49. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_typing.py +0 -0
  50. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_undefined_concept.py +0 -0
  51. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_user_functions.py +0 -0
  52. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/tests/test_where_clause.py +0 -0
  53. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/authoring/__init__.py +0 -0
  54. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/compiler.py +0 -0
  55. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/constants.py +0 -0
  56. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/__init__.py +0 -0
  57. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/constants.py +0 -0
  58. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/enums.py +0 -0
  59. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/env_processor.py +0 -0
  60. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/environment_helpers.py +0 -0
  61. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/ergonomics.py +0 -0
  62. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/exceptions.py +0 -0
  63. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/graph_models.py +0 -0
  64. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/internal.py +0 -0
  65. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/__init__.py +0 -0
  66. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/build_environment.py +0 -0
  67. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/core.py +0 -0
  68. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/datasource.py +0 -0
  69. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/environment.py +0 -0
  70. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/models/execute.py +0 -0
  71. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/optimization.py +0 -0
  72. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/optimizations/__init__.py +0 -0
  73. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/optimizations/base_optimization.py +0 -0
  74. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/optimizations/inline_datasource.py +0 -0
  75. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  76. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/__init__.py +0 -0
  77. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  78. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/discovery_loop.py +0 -0
  79. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/discovery_utility.py +0 -0
  80. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/discovery_validation.py +0 -0
  81. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/graph_utils.py +0 -0
  82. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/__init__.py +0 -0
  83. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  84. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/common.py +0 -0
  85. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  86. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  87. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  88. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  89. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  90. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  91. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  92. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  93. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/select_node.py +0 -0
  94. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  95. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/union_node.py +0 -0
  96. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  97. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/node_generators/window_node.py +0 -0
  98. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/__init__.py +0 -0
  99. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/base_node.py +0 -0
  100. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/filter_node.py +0 -0
  101. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/group_node.py +0 -0
  102. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/merge_node.py +0 -0
  103. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  104. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  105. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/union_node.py +0 -0
  106. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  107. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/nodes/window_node.py +0 -0
  108. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/processing/utility.py +0 -0
  109. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/statements/__init__.py +0 -0
  110. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/statements/author.py +0 -0
  111. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/statements/build.py +0 -0
  112. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/statements/common.py +0 -0
  113. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/statements/execute.py +0 -0
  114. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/core/utility.py +0 -0
  115. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/__init__.py +0 -0
  116. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/common.py +0 -0
  117. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/config.py +0 -0
  118. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/dataframe.py +0 -0
  119. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/enums.py +0 -0
  120. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/postgres.py +0 -0
  121. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/presto.py +0 -0
  122. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/snowflake.py +0 -0
  123. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/dialect/sql_server.py +0 -0
  124. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/engine.py +0 -0
  125. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/executor.py +0 -0
  126. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/hooks/__init__.py +0 -0
  127. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/hooks/base_hook.py +0 -0
  128. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/hooks/graph_hook.py +0 -0
  129. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/hooks/query_debugger.py +0 -0
  130. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/metadata/__init__.py +0 -0
  131. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parser.py +0 -0
  132. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/__init__.py +0 -0
  133. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/config.py +0 -0
  134. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/exceptions.py +0 -0
  135. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/helpers.py +0 -0
  136. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/parsing/render.py +0 -0
  137. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/py.typed +0 -0
  138. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/render.py +0 -0
  139. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/scripts/__init__.py +0 -0
  140. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/scripts/trilogy.py +0 -0
  141. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/__init__.py +0 -0
  142. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/date.preql +0 -0
  143. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/display.preql +0 -0
  144. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/geography.preql +0 -0
  145. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/money.preql +0 -0
  146. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/net.preql +0 -0
  147. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/ranking.preql +0 -0
  148. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/std/report.preql +0 -0
  149. {pytrilogy-0.0.3.68 → pytrilogy-0.0.3.70}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.68
3
+ Version: 0.0.3.70
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -40,46 +40,61 @@ Dynamic: summary
40
40
  [![Website](https://img.shields.io/badge/INTRO-WEB-orange?)](https://trilogydata.dev/)
41
41
  [![Discord](https://img.shields.io/badge/DISCORD-CHAT-red?logo=discord)](https://discord.gg/Z4QSSuqGEd)
42
42
 
43
- pytrilogy is an experimental implementation of the Trilogy language, a higher-level SQL that replaces tables/joins with a lightweight semantic binding layer.
43
+ The Trilogy language is an experiment in better SQL for analytics - a streamlined SQL that replaces tables/joins with a lightweight semantic binding layer and provides easy reuse and composability. It compiles to SQL - making it easy to debug or integrate into existing workflows - and can be run against any supported SQL backend.
44
44
 
45
- Trilogy looks like SQL, but simpler. It's a modern SQL refresh targeted at SQL lovers who want more reusability and composability without losing the expressiveness and iterative value of SQL. It compiles to SQL - making it easy to debug or integrate into existing workflows - and can be run against any supported SQL backend.
45
+ [pytrilogy](https://github.com/trilogy-data/pytrilogy) is the reference implementation, written in Python.
46
+
47
+ Trilogy concretely solves these common problems in karge, SQL based analytics teams:
48
+ - decoupling consumption code from specific physical assets
49
+ - better testability and change management
50
+ - reduced boilerplate and opportunity for OLAP style optimization at scale
51
+
52
+ Trilogy can be especially powerful as a frontend consumption language, since the decoupling from the physical layout makes dynamic and interactive dashboards backed by SQL tables much easier to create.
46
53
 
47
54
  > [!TIP]
48
- > Try it online in a hosted [open-source studio](https://trilogydata.dev/trilogy-studio-core/). To get an overview of the language and run interactive examples, head to the [documentation](https://trilogydata.dev/).
55
+ > You can try Trilogy in a [open-source studio](https://trilogydata.dev/trilogy-studio-core/). More details on the language can be found on the [documentation](https://trilogydata.dev/).
49
56
 
50
- Installation: `pip install pytrilogy`
57
+ We recommend the studio as the fastest way to explore Trilogy. For deeper work and integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
51
58
 
52
- `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
59
+ Installation: `pip install pytrilogy`
53
60
 
54
- You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
61
+ ### Trilogy Looks Like SQL
55
62
 
56
- Trilogy looks like SQL:
57
63
  ```sql
64
+ import names;
65
+
66
+ const top_names <- ['Elvis', 'Elvira', 'Elrond', 'Sam'];
67
+
68
+ def initcap(word) -> upper(substring(word, 1, 1)) || substring(word, 2, len(word));
69
+
58
70
  WHERE
59
- name like '%lvis%'
71
+ @initcap(name) in top_names
60
72
  SELECT
61
73
  name,
62
- count(name) as name_count
74
+ sum(births) as name_count
63
75
  ORDER BY
64
76
  name_count desc
65
77
  LIMIT 10;
66
78
  ```
67
79
  ## Goals
68
- And aims to:
80
+ Versus SQL, Trilogy aims to:
69
81
 
70
- Preserve:
82
+ Keep:
71
83
  - Correctness
72
84
  - Accessibility
73
85
 
74
- Enhance:
86
+ Improve:
75
87
  - Simplicity
76
- - Understandability
77
88
  - Refactoring/mantainability
78
89
  - Reusability
79
90
 
80
91
  Maintain:
81
92
  - Acceptable performance
82
93
 
94
+ Remove:
95
+ - Lower-level procedural features
96
+ - Transactional optimizations/non-analytics features
97
+
83
98
  ## Hello World
84
99
 
85
100
  Save the following code in a file named `hello.preql`
@@ -141,10 +156,7 @@ SELECT
141
156
  sentences.text
142
157
  ;
143
158
 
144
-
145
-
146
159
  ```
147
-
148
160
  Run the following from the directory the file is in.
149
161
 
150
162
  ```bash
@@ -157,11 +169,15 @@ trilogy run hello.trilogy duckdb
157
169
 
158
170
  The current Trilogy implementation supports these backends:
159
171
 
172
+ ### Core
160
173
  - Bigquery
161
- - SQL Server
162
174
  - DuckDB
163
175
  - Snowflake
164
176
 
177
+ ### Experimental
178
+ - SQL Server
179
+ - Presto
180
+
165
181
  ## Basic Example - Python
166
182
 
167
183
  Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
@@ -170,7 +186,6 @@ A bigquery example, similar to bigquery [the quickstart](https://cloud.google.co
170
186
 
171
187
  ```python
172
188
 
173
-
174
189
  from trilogy import Dialects, Environment
175
190
 
176
191
  environment = Environment()
@@ -2,46 +2,61 @@
2
2
  [![Website](https://img.shields.io/badge/INTRO-WEB-orange?)](https://trilogydata.dev/)
3
3
  [![Discord](https://img.shields.io/badge/DISCORD-CHAT-red?logo=discord)](https://discord.gg/Z4QSSuqGEd)
4
4
 
5
- pytrilogy is an experimental implementation of the Trilogy language, a higher-level SQL that replaces tables/joins with a lightweight semantic binding layer.
5
+ The Trilogy language is an experiment in better SQL for analytics - a streamlined SQL that replaces tables/joins with a lightweight semantic binding layer and provides easy reuse and composability. It compiles to SQL - making it easy to debug or integrate into existing workflows - and can be run against any supported SQL backend.
6
6
 
7
- Trilogy looks like SQL, but simpler. It's a modern SQL refresh targeted at SQL lovers who want more reusability and composability without losing the expressiveness and iterative value of SQL. It compiles to SQL - making it easy to debug or integrate into existing workflows - and can be run against any supported SQL backend.
7
+ [pytrilogy](https://github.com/trilogy-data/pytrilogy) is the reference implementation, written in Python.
8
+
9
+ Trilogy concretely solves these common problems in karge, SQL based analytics teams:
10
+ - decoupling consumption code from specific physical assets
11
+ - better testability and change management
12
+ - reduced boilerplate and opportunity for OLAP style optimization at scale
13
+
14
+ Trilogy can be especially powerful as a frontend consumption language, since the decoupling from the physical layout makes dynamic and interactive dashboards backed by SQL tables much easier to create.
8
15
 
9
16
  > [!TIP]
10
- > Try it online in a hosted [open-source studio](https://trilogydata.dev/trilogy-studio-core/). To get an overview of the language and run interactive examples, head to the [documentation](https://trilogydata.dev/).
17
+ > You can try Trilogy in a [open-source studio](https://trilogydata.dev/trilogy-studio-core/). More details on the language can be found on the [documentation](https://trilogydata.dev/).
11
18
 
12
- Installation: `pip install pytrilogy`
19
+ We recommend the studio as the fastest way to explore Trilogy. For deeper work and integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
13
20
 
14
- `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
21
+ Installation: `pip install pytrilogy`
15
22
 
16
- You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
23
+ ### Trilogy Looks Like SQL
17
24
 
18
- Trilogy looks like SQL:
19
25
  ```sql
26
+ import names;
27
+
28
+ const top_names <- ['Elvis', 'Elvira', 'Elrond', 'Sam'];
29
+
30
+ def initcap(word) -> upper(substring(word, 1, 1)) || substring(word, 2, len(word));
31
+
20
32
  WHERE
21
- name like '%lvis%'
33
+ @initcap(name) in top_names
22
34
  SELECT
23
35
  name,
24
- count(name) as name_count
36
+ sum(births) as name_count
25
37
  ORDER BY
26
38
  name_count desc
27
39
  LIMIT 10;
28
40
  ```
29
41
  ## Goals
30
- And aims to:
42
+ Versus SQL, Trilogy aims to:
31
43
 
32
- Preserve:
44
+ Keep:
33
45
  - Correctness
34
46
  - Accessibility
35
47
 
36
- Enhance:
48
+ Improve:
37
49
  - Simplicity
38
- - Understandability
39
50
  - Refactoring/mantainability
40
51
  - Reusability
41
52
 
42
53
  Maintain:
43
54
  - Acceptable performance
44
55
 
56
+ Remove:
57
+ - Lower-level procedural features
58
+ - Transactional optimizations/non-analytics features
59
+
45
60
  ## Hello World
46
61
 
47
62
  Save the following code in a file named `hello.preql`
@@ -103,10 +118,7 @@ SELECT
103
118
  sentences.text
104
119
  ;
105
120
 
106
-
107
-
108
121
  ```
109
-
110
122
  Run the following from the directory the file is in.
111
123
 
112
124
  ```bash
@@ -119,11 +131,15 @@ trilogy run hello.trilogy duckdb
119
131
 
120
132
  The current Trilogy implementation supports these backends:
121
133
 
134
+ ### Core
122
135
  - Bigquery
123
- - SQL Server
124
136
  - DuckDB
125
137
  - Snowflake
126
138
 
139
+ ### Experimental
140
+ - SQL Server
141
+ - Presto
142
+
127
143
  ## Basic Example - Python
128
144
 
129
145
  Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
@@ -132,7 +148,6 @@ A bigquery example, similar to bigquery [the quickstart](https://cloud.google.co
132
148
 
133
149
  ```python
134
150
 
135
-
136
151
  from trilogy import Dialects, Environment
137
152
 
138
153
  environment = Environment()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.68
3
+ Version: 0.0.3.70
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -40,46 +40,61 @@ Dynamic: summary
40
40
  [![Website](https://img.shields.io/badge/INTRO-WEB-orange?)](https://trilogydata.dev/)
41
41
  [![Discord](https://img.shields.io/badge/DISCORD-CHAT-red?logo=discord)](https://discord.gg/Z4QSSuqGEd)
42
42
 
43
- pytrilogy is an experimental implementation of the Trilogy language, a higher-level SQL that replaces tables/joins with a lightweight semantic binding layer.
43
+ The Trilogy language is an experiment in better SQL for analytics - a streamlined SQL that replaces tables/joins with a lightweight semantic binding layer and provides easy reuse and composability. It compiles to SQL - making it easy to debug or integrate into existing workflows - and can be run against any supported SQL backend.
44
44
 
45
- Trilogy looks like SQL, but simpler. It's a modern SQL refresh targeted at SQL lovers who want more reusability and composability without losing the expressiveness and iterative value of SQL. It compiles to SQL - making it easy to debug or integrate into existing workflows - and can be run against any supported SQL backend.
45
+ [pytrilogy](https://github.com/trilogy-data/pytrilogy) is the reference implementation, written in Python.
46
+
47
+ Trilogy concretely solves these common problems in karge, SQL based analytics teams:
48
+ - decoupling consumption code from specific physical assets
49
+ - better testability and change management
50
+ - reduced boilerplate and opportunity for OLAP style optimization at scale
51
+
52
+ Trilogy can be especially powerful as a frontend consumption language, since the decoupling from the physical layout makes dynamic and interactive dashboards backed by SQL tables much easier to create.
46
53
 
47
54
  > [!TIP]
48
- > Try it online in a hosted [open-source studio](https://trilogydata.dev/trilogy-studio-core/). To get an overview of the language and run interactive examples, head to the [documentation](https://trilogydata.dev/).
55
+ > You can try Trilogy in a [open-source studio](https://trilogydata.dev/trilogy-studio-core/). More details on the language can be found on the [documentation](https://trilogydata.dev/).
49
56
 
50
- Installation: `pip install pytrilogy`
57
+ We recommend the studio as the fastest way to explore Trilogy. For deeper work and integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
51
58
 
52
- `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
59
+ Installation: `pip install pytrilogy`
53
60
 
54
- You can read more about the project [here](https://trilogydata.dev/) and try out an interactive demo [here](https://trilogydata.dev/demo/).
61
+ ### Trilogy Looks Like SQL
55
62
 
56
- Trilogy looks like SQL:
57
63
  ```sql
64
+ import names;
65
+
66
+ const top_names <- ['Elvis', 'Elvira', 'Elrond', 'Sam'];
67
+
68
+ def initcap(word) -> upper(substring(word, 1, 1)) || substring(word, 2, len(word));
69
+
58
70
  WHERE
59
- name like '%lvis%'
71
+ @initcap(name) in top_names
60
72
  SELECT
61
73
  name,
62
- count(name) as name_count
74
+ sum(births) as name_count
63
75
  ORDER BY
64
76
  name_count desc
65
77
  LIMIT 10;
66
78
  ```
67
79
  ## Goals
68
- And aims to:
80
+ Versus SQL, Trilogy aims to:
69
81
 
70
- Preserve:
82
+ Keep:
71
83
  - Correctness
72
84
  - Accessibility
73
85
 
74
- Enhance:
86
+ Improve:
75
87
  - Simplicity
76
- - Understandability
77
88
  - Refactoring/mantainability
78
89
  - Reusability
79
90
 
80
91
  Maintain:
81
92
  - Acceptable performance
82
93
 
94
+ Remove:
95
+ - Lower-level procedural features
96
+ - Transactional optimizations/non-analytics features
97
+
83
98
  ## Hello World
84
99
 
85
100
  Save the following code in a file named `hello.preql`
@@ -141,10 +156,7 @@ SELECT
141
156
  sentences.text
142
157
  ;
143
158
 
144
-
145
-
146
159
  ```
147
-
148
160
  Run the following from the directory the file is in.
149
161
 
150
162
  ```bash
@@ -157,11 +169,15 @@ trilogy run hello.trilogy duckdb
157
169
 
158
170
  The current Trilogy implementation supports these backends:
159
171
 
172
+ ### Core
160
173
  - Bigquery
161
- - SQL Server
162
174
  - DuckDB
163
175
  - Snowflake
164
176
 
177
+ ### Experimental
178
+ - SQL Server
179
+ - Presto
180
+
165
181
  ## Basic Example - Python
166
182
 
167
183
  Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
@@ -170,7 +186,6 @@ A bigquery example, similar to bigquery [the quickstart](https://cloud.google.co
170
186
 
171
187
  ```python
172
188
 
173
-
174
189
  from trilogy import Dialects, Environment
175
190
 
176
191
  environment = Environment()
@@ -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.68"
7
+ __version__ = "0.0.3.70"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -370,10 +370,10 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
370
370
  arg_count=2,
371
371
  ),
372
372
  FunctionType.REGEXP_EXTRACT: FunctionConfig(
373
- valid_inputs={DataType.STRING},
373
+ valid_inputs=[{DataType.STRING}, {DataType.STRING}, {DataType.INTEGER}],
374
374
  output_purpose=Purpose.PROPERTY,
375
375
  output_type=DataType.STRING,
376
- arg_count=2,
376
+ arg_count=3,
377
377
  ),
378
378
  FunctionType.REGEXP_REPLACE: FunctionConfig(
379
379
  valid_inputs={DataType.STRING},
@@ -2303,7 +2303,7 @@ class CustomFunctionFactory:
2303
2303
  ]
2304
2304
  return self
2305
2305
 
2306
- def __call__(self, *creation_args: list[Expr]):
2306
+ def __call__(self, *creation_args: Expr):
2307
2307
  nout = (
2308
2308
  self.function.model_copy(deep=True)
2309
2309
  if isinstance(self.function, BaseModel)
@@ -2389,7 +2389,6 @@ Expr = (
2389
2389
  | int
2390
2390
  | str
2391
2391
  | float
2392
- | list
2393
2392
  | date
2394
2393
  | datetime
2395
2394
  | TupleWrapper
@@ -2430,7 +2429,6 @@ FuncArgs = (
2430
2429
  | ListType
2431
2430
  | MapType
2432
2431
  | NumericType
2433
- | list
2434
2432
  | ListWrapper[Any]
2435
2433
  | TupleWrapper[Any]
2436
2434
  | Comparison
@@ -155,6 +155,7 @@ def concepts_to_build_grain_concepts(
155
155
  pconcepts.append(c)
156
156
  elif environment:
157
157
  pconcepts.append(environment.concepts[c])
158
+
158
159
  else:
159
160
  raise ValueError(
160
161
  f"Unable to resolve input {c} without environment provided to concepts_to_grain call"
@@ -248,7 +249,7 @@ def get_concept_arguments(expr) -> List["BuildConcept"]:
248
249
  return output
249
250
 
250
251
 
251
- class BuildParamaterizedConceptReference(BaseModel):
252
+ class BuildParamaterizedConceptReference(DataTyped, BaseModel):
252
253
  concept: BuildConcept
253
254
 
254
255
  def __str__(self):
@@ -258,6 +259,10 @@ class BuildParamaterizedConceptReference(BaseModel):
258
259
  def safe_address(self) -> str:
259
260
  return self.concept.safe_address
260
261
 
262
+ @property
263
+ def output_datatype(self) -> DataType:
264
+ return self.concept.output_datatype
265
+
261
266
 
262
267
  class BuildGrain(BaseModel):
263
268
  components: set[str] = Field(default_factory=set)
@@ -1810,8 +1815,8 @@ class Factory:
1810
1815
  right_c, _ = self.instantiate_concept(base.right)
1811
1816
  right = right_c
1812
1817
  return BuildSubselectComparison.model_construct(
1813
- left=self.build(base.left),
1814
- right=self.build(right),
1818
+ left=self.handle_constant(self.build(base.left)),
1819
+ right=self.handle_constant(self.build(right)),
1815
1820
  operator=base.operator,
1816
1821
  )
1817
1822
 
@@ -1916,7 +1921,17 @@ class Factory:
1916
1921
  where_factory = Factory(
1917
1922
  grain=Grain(), environment=self.environment, local_concepts={}
1918
1923
  )
1919
-
1924
+ where_clause = (
1925
+ where_factory.build(base.where_clause) if base.where_clause else None
1926
+ )
1927
+ # if the where clause derives new concepts
1928
+ # we need to ensure these are accessible from the general factory
1929
+ # post resolution
1930
+ for bk, bv in where_factory.local_concepts.items():
1931
+ # but do not override any local cahced grains
1932
+ if bk in materialized:
1933
+ continue
1934
+ materialized[bk] = bv
1920
1935
  final: List[BuildConcept] = []
1921
1936
  for original in base.selection:
1922
1937
  new = original
@@ -1943,9 +1958,7 @@ class Factory:
1943
1958
  factory.build(base.having_clause) if base.having_clause else None
1944
1959
  ),
1945
1960
  # this uses a different grain factory
1946
- where_clause=(
1947
- where_factory.build(base.where_clause) if base.where_clause else None
1948
- ),
1961
+ where_clause=where_clause,
1949
1962
  )
1950
1963
 
1951
1964
  @build.register
@@ -469,6 +469,7 @@ def generate_node(
469
469
  Derivation.GROUP_TO: lambda: _generate_group_to_node(context),
470
470
  Derivation.BASIC: lambda: _generate_basic_node(context),
471
471
  Derivation.ROOT: lambda: RootNodeHandler(context).generate(),
472
+ Derivation.CONSTANT: lambda: RootNodeHandler(context).generate(),
472
473
  }
473
474
 
474
475
  handler = derivation_handlers.get(concept.derivation)
@@ -22,6 +22,24 @@ from trilogy.utility import unique
22
22
  LOGGER_PREFIX = "[GEN_GROUP_NODE]"
23
23
 
24
24
 
25
+ def get_aggregate_grain(
26
+ concept: BuildConcept, environment: BuildEnvironment
27
+ ) -> BuildGrain:
28
+ parent_concepts: List[BuildConcept] = unique(
29
+ resolve_function_parent_concepts(concept, environment=environment), "address"
30
+ )
31
+ if (
32
+ concept.grain
33
+ and len(concept.grain.components) > 0
34
+ and not concept.grain.abstract
35
+ ):
36
+ grain_components = [environment.concepts[c] for c in concept.grain.components]
37
+ parent_concepts += grain_components
38
+ return BuildGrain.from_concepts(parent_concepts)
39
+ else:
40
+ return BuildGrain.from_concepts(parent_concepts)
41
+
42
+
25
43
  def gen_group_node(
26
44
  concept: BuildConcept,
27
45
  local_optional: List[BuildConcept],
@@ -51,7 +69,7 @@ def gen_group_node(
51
69
  ):
52
70
  grain_components = [environment.concepts[c] for c in concept.grain.components]
53
71
  parent_concepts += grain_components
54
- build_grain_parents = BuildGrain.from_concepts(parent_concepts)
72
+ build_grain_parents = get_aggregate_grain(concept, environment)
55
73
  output_concepts += grain_components
56
74
  for possible_agg in local_optional:
57
75
 
@@ -70,6 +88,7 @@ def gen_group_node(
70
88
  possible_agg,
71
89
  environment=environment,
72
90
  )
91
+ comp_grain = get_aggregate_grain(possible_agg, environment)
73
92
  if set([x.address for x in agg_parents]).issubset(
74
93
  set([x.address for x in parent_concepts])
75
94
  ):
@@ -77,7 +96,7 @@ def gen_group_node(
77
96
  logger.info(
78
97
  f"{padding(depth)}{LOGGER_PREFIX} found equivalent group by optional concept {possible_agg.address} for {concept.address}"
79
98
  )
80
- elif BuildGrain.from_concepts(agg_parents) == build_grain_parents:
99
+ elif comp_grain == build_grain_parents:
81
100
  extra = [x for x in agg_parents if x.address not in parent_concepts]
82
101
  parent_concepts += extra
83
102
  output_concepts.append(possible_agg)
@@ -86,7 +105,7 @@ def gen_group_node(
86
105
  )
87
106
  else:
88
107
  logger.info(
89
- f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg {possible_agg.address}; mismatched parent grain {BuildGrain.from_concepts(agg_parents)} vs local parent {BuildGrain.from_concepts(parent_concepts)}"
108
+ f"{padding(depth)}{LOGGER_PREFIX} cannot include optional agg {possible_agg.address}; it has mismatched parent grain {comp_grain } vs local parent {build_grain_parents}"
90
109
  )
91
110
  if parent_concepts:
92
111
  logger.info(
@@ -231,7 +231,10 @@ def create_pruned_concept_graph(
231
231
 
232
232
 
233
233
  def resolve_subgraphs(
234
- g: nx.DiGraph, relevant: list[BuildConcept], conditions: BuildWhereClause | None
234
+ g: nx.DiGraph,
235
+ relevant: list[BuildConcept],
236
+ conditions: BuildWhereClause | None,
237
+ depth: int = 0,
235
238
  ) -> dict[str, list[str]]:
236
239
  """When we have multiple distinct subgraphs within our matched
237
240
  nodes that can satisfy a query, resolve which one of those we should
@@ -241,7 +244,7 @@ def resolve_subgraphs(
241
244
  discarding duplicates.
242
245
  Duplicate subgraphs will be resolved based on which
243
246
  ones are most 'optimal' to use, a hueristic
244
- that can evolve in the future but is currently based on
247
+ that can evolve in the future but is currently based on datasource
245
248
  cardinality."""
246
249
  datasources = [n for n in g.nodes if n.startswith("ds~")]
247
250
  subgraphs: dict[str, list[str]] = {
@@ -261,7 +264,7 @@ def resolve_subgraphs(
261
264
  pruned_subgraphs = {}
262
265
 
263
266
  def score_node(input: str):
264
- logger.debug(f"scoring node {input}")
267
+ logger.debug(f"{padding(depth)}{LOGGER_PREFIX} scoring node {input}")
265
268
  grain = grain_length[input]
266
269
  # first - go for lowest grain
267
270
  # but if the object we want is in the grain, treat that as "free"
@@ -275,7 +278,7 @@ def resolve_subgraphs(
275
278
  len(subgraphs[input]),
276
279
  input,
277
280
  )
278
- logger.debug(score)
281
+ logger.debug(f"{padding(depth)}{LOGGER_PREFIX} node {input} has score {score}")
279
282
  return score
280
283
 
281
284
  for key, nodes in subgraphs.items():
@@ -296,7 +299,7 @@ def resolve_subgraphs(
296
299
  if len(value) < len(other_value):
297
300
  is_subset = True
298
301
  logger.debug(
299
- f"Dropping subgraph {key} with {value} as it is a subset of {other_key} with {other_value}"
302
+ f"{padding(depth)}{LOGGER_PREFIX} Dropping subgraph {key} with {value} as it is a subset of {other_key} with {other_value}"
300
303
  )
301
304
  elif len(value) == len(other_value) and len(all_concepts) == len(
302
305
  other_all_concepts
@@ -305,7 +308,9 @@ def resolve_subgraphs(
305
308
  matches.add(key)
306
309
  if matches and not is_subset:
307
310
  min_node = min(matches, key=score_node)
308
- logger.debug(f"minimum source score is {min_node}")
311
+ logger.debug(
312
+ f"{padding(depth)}{LOGGER_PREFIX} minimum source score is {min_node}"
313
+ )
309
314
  is_subset = key is not min(matches, key=score_node)
310
315
  if not is_subset:
311
316
  pruned_subgraphs[key] = nodes
@@ -330,7 +335,9 @@ def resolve_subgraphs(
330
335
  > 1
331
336
  )
332
337
  if not keep:
333
- logger.debug(f"Pruning node {node} as irrelevant after subgraph resolution")
338
+ logger.debug(
339
+ f"{padding(depth)}{LOGGER_PREFIX} Pruning node {node} as irrelevant after subgraph resolution"
340
+ )
334
341
  pruned_subgraphs = {
335
342
  k: [n for n in v if n != node] for k, v in pruned_subgraphs.items()
336
343
  }
@@ -561,7 +568,7 @@ def gen_select_merge_node(
561
568
  return None
562
569
 
563
570
  sub_nodes = resolve_subgraphs(
564
- pruned_concept_graph, relevant=non_constant, conditions=conditions
571
+ pruned_concept_graph, relevant=non_constant, conditions=conditions, depth=depth
565
572
  )
566
573
 
567
574
  logger.info(f"{padding(depth)}{LOGGER_PREFIX} fetching subgraphs {sub_nodes}")
@@ -395,14 +395,13 @@ def get_query_node(
395
395
  if not statement.output_components:
396
396
  raise ValueError(f"Statement has no output components {statement}")
397
397
  history = history or History(base_environment=environment)
398
- print(
398
+ logger.info(
399
399
  f"{LOGGER_PREFIX} building query node for {statement.output_components} grain {statement.grain}"
400
400
  )
401
401
  build_statement: BuildSelectLineage | BuildMultiSelectLineage = Factory(
402
402
  environment=environment,
403
403
  ).build(statement)
404
404
 
405
- # build_statement = statement
406
405
  build_environment = environment.materialize_for_select(
407
406
  build_statement.local_concepts
408
407
  )