pytrilogy 0.0.2.20__tar.gz → 0.0.2.22__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 (107) hide show
  1. {pytrilogy-0.0.2.20/pytrilogy.egg-info → pytrilogy-0.0.2.22}/PKG-INFO +17 -11
  2. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/README.md +16 -10
  3. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22/pytrilogy.egg-info}/PKG-INFO +17 -11
  4. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/SOURCES.txt +1 -0
  5. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_models.py +6 -2
  6. pytrilogy-0.0.2.22/tests/test_show.py +58 -0
  7. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/__init__.py +1 -1
  8. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/constants.py +1 -0
  9. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/constants.py +1 -0
  10. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/enums.py +4 -0
  11. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/env_processor.py +4 -2
  12. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/ergonomics.py +11 -4
  13. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/models.py +103 -28
  14. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/concept_strategies_v3.py +0 -1
  15. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/node_merge_node.py +19 -6
  16. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/rowset_node.py +2 -2
  17. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/select_node.py +2 -1
  18. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/query_processor.py +8 -4
  19. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/base.py +2 -0
  20. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/executor.py +14 -1
  21. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/common.py +16 -2
  22. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/parse_engine.py +1 -34
  23. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/render.py +2 -0
  24. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/trilogy.lark +35 -18
  25. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/LICENSE.md +0 -0
  26. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pyproject.toml +0 -0
  27. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/dependency_links.txt +0 -0
  28. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/entry_points.txt +0 -0
  29. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/requires.txt +0 -0
  30. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/pytrilogy.egg-info/top_level.txt +0 -0
  31. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/setup.cfg +0 -0
  32. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/setup.py +0 -0
  33. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_datatypes.py +0 -0
  34. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_declarations.py +0 -0
  35. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_derived_concepts.py +0 -0
  36. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_discovery_nodes.py +0 -0
  37. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_environment.py +0 -0
  38. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_functions.py +0 -0
  39. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_imports.py +0 -0
  40. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_metadata.py +0 -0
  41. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_multi_join_assignments.py +0 -0
  42. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_parsing.py +0 -0
  43. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_partial_handling.py +0 -0
  44. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_query_processing.py +0 -0
  45. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_select.py +0 -0
  46. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_statements.py +0 -0
  47. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_undefined_concept.py +0 -0
  48. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/tests/test_where_clause.py +0 -0
  49. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/compiler.py +0 -0
  50. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/__init__.py +0 -0
  51. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/environment_helpers.py +0 -0
  52. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/exceptions.py +0 -0
  53. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/functions.py +0 -0
  54. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/graph_models.py +0 -0
  55. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/internal.py +0 -0
  56. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimization.py +0 -0
  57. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/__init__.py +0 -0
  58. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/base_optimization.py +0 -0
  59. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/inline_constant.py +0 -0
  60. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/inline_datasource.py +0 -0
  61. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  62. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/__init__.py +0 -0
  63. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/graph_utils.py +0 -0
  64. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/__init__.py +0 -0
  65. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  66. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/common.py +0 -0
  67. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  68. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/group_node.py +0 -0
  69. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  70. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  71. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  72. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  73. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/node_generators/window_node.py +0 -0
  74. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/__init__.py +0 -0
  75. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/base_node.py +0 -0
  76. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/filter_node.py +0 -0
  77. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/group_node.py +0 -0
  78. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/merge_node.py +0 -0
  79. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  80. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  81. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/nodes/window_node.py +0 -0
  82. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/core/processing/utility.py +0 -0
  83. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/__init__.py +0 -0
  84. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/bigquery.py +0 -0
  85. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/common.py +0 -0
  86. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/config.py +0 -0
  87. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/duckdb.py +0 -0
  88. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/enums.py +0 -0
  89. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/postgres.py +0 -0
  90. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/presto.py +0 -0
  91. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/snowflake.py +0 -0
  92. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/dialect/sql_server.py +0 -0
  93. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/engine.py +0 -0
  94. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/__init__.py +0 -0
  95. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/base_hook.py +0 -0
  96. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/graph_hook.py +0 -0
  97. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/hooks/query_debugger.py +0 -0
  98. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/metadata/__init__.py +0 -0
  99. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parser.py +0 -0
  100. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/__init__.py +0 -0
  101. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/config.py +0 -0
  102. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/exceptions.py +0 -0
  103. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/parsing/helpers.py +0 -0
  104. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/py.typed +0 -0
  105. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/scripts/__init__.py +0 -0
  106. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/scripts/trilogy.py +0 -0
  107. {pytrilogy-0.0.2.20 → pytrilogy-0.0.2.22}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.20
3
+ Version: 0.0.2.22
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -145,7 +145,7 @@ Run the following from the directory the file is in.
145
145
  trilogy run hello.trilogy duckdb
146
146
  ```
147
147
 
148
- ![UI Preview](./hello_world.png)
148
+ ![UI Preview](hello-world.png)
149
149
 
150
150
  ## Backends
151
151
 
@@ -158,9 +158,9 @@ The current Trilogy implementation supports these backends:
158
158
 
159
159
  ## Basic Example - Python
160
160
 
161
- Trilogy can be run directly in python.
161
+ Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
162
162
 
163
- A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console)
163
+ A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console).
164
164
 
165
165
  ```python
166
166
 
@@ -224,7 +224,7 @@ and second the dialect to run.
224
224
  To pass arguments to a backend, append additional --<option> flags after specifying the dialect.
225
225
 
226
226
  Example:
227
- `trilogy run key in int; datasource test_source ( i:in) grain(in) address test; select in;" duckdb --path <path/to/duckdb>`
227
+ `trilogy run "key x int; datasource test_source ( i:x) grain(in) address test; select x;" duckdb --path <path/to/database>`
228
228
 
229
229
  ### Bigquery Args
230
230
  N/A, only supports default auth. In python you can pass in a custom client.
@@ -267,7 +267,7 @@ Clone repository and install requirements.txt and requirements-test.txt.
267
267
  Please open an issue first to discuss what you would like to change, and then create a PR against that issue.
268
268
 
269
269
  ## Similar in space
270
- Trilogy combines two aspects; a semantic layer and a query language. We've covered examples of both below:
270
+ Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
271
271
 
272
272
  Python "semantic layers" are tools for defining data access to a warehouse in a more abstract way.
273
273
 
@@ -284,20 +284,26 @@ but all are worth checking out. Please open PRs/comment for anything missed!
284
284
 
285
285
  #### IMPORT
286
286
 
287
- `import <path> as <alias>;`
287
+ `import [path] as [alias];`
288
288
 
289
289
  #### CONCEPT
290
290
 
291
- Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval`;
291
+ Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval | list<[type]> | map<[type], [type]> | struct<name:[type], name:[type]>`;
292
292
 
293
293
  Key:
294
- `key <name> <type>;`
294
+ `key [name] [type];`
295
295
 
296
296
  Property:
297
- `property <key>.<name> <type>;`
297
+ `property [key>].[name] [type];`
298
+ `property x.y int;`
299
+ or
300
+ `property <[key](,[key])?>.<name> [type];`
301
+ `property <x,y>.z int;`
302
+
298
303
 
299
304
  Transformation:
300
- `auto <name> <- <expression>;`
305
+ `auto [name] <- [expression];`
306
+ `auto x <- y + 1;`
301
307
 
302
308
  #### DATASOURCE
303
309
  ```sql
@@ -116,7 +116,7 @@ Run the following from the directory the file is in.
116
116
  trilogy run hello.trilogy duckdb
117
117
  ```
118
118
 
119
- ![UI Preview](./hello_world.png)
119
+ ![UI Preview](hello-world.png)
120
120
 
121
121
  ## Backends
122
122
 
@@ -129,9 +129,9 @@ The current Trilogy implementation supports these backends:
129
129
 
130
130
  ## Basic Example - Python
131
131
 
132
- Trilogy can be run directly in python.
132
+ Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
133
133
 
134
- A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console)
134
+ A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console).
135
135
 
136
136
  ```python
137
137
 
@@ -195,7 +195,7 @@ and second the dialect to run.
195
195
  To pass arguments to a backend, append additional --<option> flags after specifying the dialect.
196
196
 
197
197
  Example:
198
- `trilogy run key in int; datasource test_source ( i:in) grain(in) address test; select in;" duckdb --path <path/to/duckdb>`
198
+ `trilogy run "key x int; datasource test_source ( i:x) grain(in) address test; select x;" duckdb --path <path/to/database>`
199
199
 
200
200
  ### Bigquery Args
201
201
  N/A, only supports default auth. In python you can pass in a custom client.
@@ -238,7 +238,7 @@ Clone repository and install requirements.txt and requirements-test.txt.
238
238
  Please open an issue first to discuss what you would like to change, and then create a PR against that issue.
239
239
 
240
240
  ## Similar in space
241
- Trilogy combines two aspects; a semantic layer and a query language. We've covered examples of both below:
241
+ Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
242
242
 
243
243
  Python "semantic layers" are tools for defining data access to a warehouse in a more abstract way.
244
244
 
@@ -255,20 +255,26 @@ but all are worth checking out. Please open PRs/comment for anything missed!
255
255
 
256
256
  #### IMPORT
257
257
 
258
- `import <path> as <alias>;`
258
+ `import [path] as [alias];`
259
259
 
260
260
  #### CONCEPT
261
261
 
262
- Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval`;
262
+ Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval | list<[type]> | map<[type], [type]> | struct<name:[type], name:[type]>`;
263
263
 
264
264
  Key:
265
- `key <name> <type>;`
265
+ `key [name] [type];`
266
266
 
267
267
  Property:
268
- `property <key>.<name> <type>;`
268
+ `property [key>].[name] [type];`
269
+ `property x.y int;`
270
+ or
271
+ `property <[key](,[key])?>.<name> [type];`
272
+ `property <x,y>.z int;`
273
+
269
274
 
270
275
  Transformation:
271
- `auto <name> <- <expression>;`
276
+ `auto [name] <- [expression];`
277
+ `auto x <- y + 1;`
272
278
 
273
279
  #### DATASOURCE
274
280
  ```sql
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.20
3
+ Version: 0.0.2.22
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -145,7 +145,7 @@ Run the following from the directory the file is in.
145
145
  trilogy run hello.trilogy duckdb
146
146
  ```
147
147
 
148
- ![UI Preview](./hello_world.png)
148
+ ![UI Preview](hello-world.png)
149
149
 
150
150
  ## Backends
151
151
 
@@ -158,9 +158,9 @@ The current Trilogy implementation supports these backends:
158
158
 
159
159
  ## Basic Example - Python
160
160
 
161
- Trilogy can be run directly in python.
161
+ Trilogy can be run directly in python through the core SDK. Trilogy code can be defined and parsed inline or parsed out of files.
162
162
 
163
- A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console)
163
+ A bigquery example, similar to bigquery [the quickstart](https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console).
164
164
 
165
165
  ```python
166
166
 
@@ -224,7 +224,7 @@ and second the dialect to run.
224
224
  To pass arguments to a backend, append additional --<option> flags after specifying the dialect.
225
225
 
226
226
  Example:
227
- `trilogy run key in int; datasource test_source ( i:in) grain(in) address test; select in;" duckdb --path <path/to/duckdb>`
227
+ `trilogy run "key x int; datasource test_source ( i:x) grain(in) address test; select x;" duckdb --path <path/to/database>`
228
228
 
229
229
  ### Bigquery Args
230
230
  N/A, only supports default auth. In python you can pass in a custom client.
@@ -267,7 +267,7 @@ Clone repository and install requirements.txt and requirements-test.txt.
267
267
  Please open an issue first to discuss what you would like to change, and then create a PR against that issue.
268
268
 
269
269
  ## Similar in space
270
- Trilogy combines two aspects; a semantic layer and a query language. We've covered examples of both below:
270
+ Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
271
271
 
272
272
  Python "semantic layers" are tools for defining data access to a warehouse in a more abstract way.
273
273
 
@@ -284,20 +284,26 @@ but all are worth checking out. Please open PRs/comment for anything missed!
284
284
 
285
285
  #### IMPORT
286
286
 
287
- `import <path> as <alias>;`
287
+ `import [path] as [alias];`
288
288
 
289
289
  #### CONCEPT
290
290
 
291
- Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval`;
291
+ Types: `string | int | float | bool | date | datetime | time | numeric(scale, precision) | timestamp | interval | list<[type]> | map<[type], [type]> | struct<name:[type], name:[type]>`;
292
292
 
293
293
  Key:
294
- `key <name> <type>;`
294
+ `key [name] [type];`
295
295
 
296
296
  Property:
297
- `property <key>.<name> <type>;`
297
+ `property [key>].[name] [type];`
298
+ `property x.y int;`
299
+ or
300
+ `property <[key](,[key])?>.<name> [type];`
301
+ `property <x,y>.z int;`
302
+
298
303
 
299
304
  Transformation:
300
- `auto <name> <- <expression>;`
305
+ `auto [name] <- [expression];`
306
+ `auto x <- y + 1;`
301
307
 
302
308
  #### DATASOURCE
303
309
  ```sql
@@ -22,6 +22,7 @@ tests/test_parsing.py
22
22
  tests/test_partial_handling.py
23
23
  tests/test_query_processing.py
24
24
  tests/test_select.py
25
+ tests/test_show.py
25
26
  tests/test_statements.py
26
27
  tests/test_undefined_concept.py
27
28
  tests/test_where_clause.py
@@ -100,12 +100,16 @@ def test_conditional(test_environment, test_environment_graph):
100
100
  merged = condition_a + condition_b
101
101
  assert merged == condition_a
102
102
 
103
- test_concept_two = list(test_environment.concepts.values())[-2]
103
+ test_concept_two = [
104
+ x
105
+ for x in test_environment.concepts.values()
106
+ if x.address != test_concept.address
107
+ ].pop()
104
108
  condition_c = Conditional(
105
109
  left=test_concept, right=test_concept_two, operator=BooleanOperator.AND
106
110
  )
107
111
  merged_two = condition_a + condition_c
108
- assert merged_two.left == condition_a
112
+ assert merged_two.left == condition_a, f"{str(merged_two.left), str(condition_a)}"
109
113
  assert merged_two.right == condition_c
110
114
  assert merged_two.operator == BooleanOperator.AND
111
115
 
@@ -0,0 +1,58 @@
1
+ from trilogy.core.models import ShowStatement
2
+ from trilogy.core.query_processor import process_query
3
+ from trilogy import Dialects
4
+ from trilogy.parser import parse
5
+
6
+
7
+ def test_show_bigquery():
8
+ declarations = """
9
+ key user_id int metadata(description="the description");
10
+ property user_id.display_name string metadata(description="The display name ");
11
+ property user_id.about_me string metadata(description="User provided description");
12
+ key post_id int;
13
+
14
+
15
+ datasource posts (
16
+ user_id: user_id,
17
+ id: post_id
18
+ )
19
+ grain (post_id)
20
+ address bigquery-public-data.stackoverflow.post_history
21
+ ;
22
+
23
+
24
+ datasource users (
25
+ id: user_id,
26
+ display_name: display_name,
27
+ about_me: about_me,
28
+ )
29
+ grain (user_id)
30
+ address bigquery-public-data.stackoverflow.users
31
+ ;
32
+
33
+
34
+ """
35
+ env, parsed = parse(declarations)
36
+
37
+ q1 = """
38
+ metric post_count<- count(post_id);
39
+ metric distinct_post_count <- count_distinct(post_id);
40
+
41
+ metric user_count <- count(user_id);
42
+
43
+ show select
44
+ post_count,
45
+ distinct_post_count,
46
+ user_count
47
+ ;"""
48
+ env, parsed = parse(q1, environment=env)
49
+ select: ShowStatement = parsed[-1]
50
+
51
+ query = (
52
+ Dialects.DUCK_DB.default_executor(environment=env)
53
+ .execute_query(select)
54
+ .fetchall()
55
+ )
56
+ assert "FULL JOIN wakeful on 1=1" in query[0]["__preql_internal_query_text"], query[
57
+ 0
58
+ ]["__preql_internal_query_text"]
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.20"
7
+ __version__ = "0.0.2.22"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -44,6 +44,7 @@ class Comments:
44
44
  class Config:
45
45
  strict_mode: bool = True
46
46
  human_identifiers: bool = True
47
+ randomize_cte_names: bool = False
47
48
  validate_missing: bool = True
48
49
  comments: Comments = field(default_factory=Comments)
49
50
  optimizations: Optimizations = field(default_factory=Optimizations)
@@ -1,3 +1,4 @@
1
1
  CONSTANT_DATASET: str = "preql_internal_constant_dataset"
2
2
  ALL_ROWS_CONCEPT = "all_rows"
3
3
  INTERNAL_NAMESPACE = "__preql_internal"
4
+ PERSISTED_CONCEPT_PREFIX = "__pre_persist"
@@ -12,6 +12,8 @@ class UnnestMode(Enum):
12
12
 
13
13
  class ConceptSource(Enum):
14
14
  MANUAL = "manual"
15
+ CTE = "cte"
16
+ PERSIST_STATEMENT = "persist_statement"
15
17
  AUTO_DERIVED = "auto_derived"
16
18
 
17
19
 
@@ -205,6 +207,8 @@ class FunctionClass(Enum):
205
207
  FunctionType.CURRENT_DATETIME,
206
208
  ]
207
209
 
210
+ ONE_TO_MANY = [FunctionType.UNNEST]
211
+
208
212
 
209
213
  class Boolean(Enum):
210
214
  TRUE = "true"
@@ -10,9 +10,11 @@ def add_concept(concept: Concept, g: ReferenceGraph):
10
10
  g.add_node(concept)
11
11
  # if we have sources, recursively add them
12
12
  node_name = concept_to_node(concept)
13
- if concept.sources:
14
- for source in concept.sources:
13
+ if concept.concept_arguments:
14
+ for source in concept.concept_arguments:
15
15
  generic = source.with_default_grain()
16
+ add_concept(generic, g)
17
+
16
18
  g.add_edge(generic, node_name)
17
19
  for _, pseudonym in concept.pseudonyms.items():
18
20
  pseudonym = pseudonym.with_default_grain()
@@ -1,3 +1,5 @@
1
+ from trilogy.constants import CONFIG
2
+
1
3
  # source: https://github.com/aaronbassett/Pass-phrase
2
4
  CTE_NAMES = """quizzical
3
5
  highfalutin
@@ -103,8 +105,6 @@ mandrill
103
105
  marlin
104
106
  monitor
105
107
  ocelot
106
- osprey
107
- owl
108
108
  petrel
109
109
  python
110
110
  ray
@@ -132,7 +132,6 @@ cuckoo
132
132
  darter
133
133
  dove
134
134
  duck
135
- eagle
136
135
  falcon
137
136
  finch
138
137
  flamingo
@@ -184,4 +183,12 @@ warbler""".split(
184
183
  "\n"
185
184
  )
186
185
 
187
- CTE_NAMES = list(set(CTE_NAMES))
186
+
187
+ def generate_cte_names():
188
+ if CONFIG.randomize_cte_names:
189
+ from random import shuffle
190
+
191
+ new = [*CTE_NAMES]
192
+ shuffle(new)
193
+ return new
194
+ return CTE_NAMES
@@ -44,6 +44,7 @@ from trilogy.core.constants import (
44
44
  ALL_ROWS_CONCEPT,
45
45
  INTERNAL_NAMESPACE,
46
46
  CONSTANT_DATASET,
47
+ PERSISTED_CONCEPT_PREFIX,
47
48
  )
48
49
  from trilogy.core.enums import (
49
50
  InfiniteFunctionArgs,
@@ -912,10 +913,10 @@ class Grain(Mergeable, BaseModel):
912
913
  )
913
914
 
914
915
  @cached_property
915
- def set(self):
916
+ def set(self) -> set[str]:
916
917
  base = []
917
918
  for x in self.components_copy:
918
- if x.derivation == PurposeLineage.ROWSET:
919
+ if isinstance(x.lineage, RowsetItem):
919
920
  base.append(x.lineage.content.address)
920
921
  else:
921
922
  base.append(x.address)
@@ -3131,6 +3132,12 @@ class EnvironmentConceptDict(dict):
3131
3132
  def values(self) -> ValuesView[Concept]: # type: ignore
3132
3133
  return super().values()
3133
3134
 
3135
+ def get(self, key: str, default: Concept | None = None) -> Concept | None: # type: ignore
3136
+ try:
3137
+ return self.__getitem__(key)
3138
+ except UndefinedConceptException:
3139
+ return default
3140
+
3134
3141
  def __getitem__(
3135
3142
  self, key, line_no: int | None = None
3136
3143
  ) -> Concept | UndefinedConcept:
@@ -3257,24 +3264,62 @@ class Environment(BaseModel):
3257
3264
  for datasource in self.datasources.values():
3258
3265
  for concept in datasource.output_concepts:
3259
3266
  concrete_addresses.add(concept.address)
3260
- self.materialized_concepts = [
3261
- c for c in self.concepts.values() if c.address in concrete_addresses
3262
- ] + [
3263
- c
3264
- for c in self.alias_origin_lookup.values()
3265
- if c.address in concrete_addresses
3266
- ]
3267
+ self.materialized_concepts = unique(
3268
+ [c for c in self.concepts.values() if c.address in concrete_addresses]
3269
+ + [
3270
+ c
3271
+ for c in self.alias_origin_lookup.values()
3272
+ if c.address in concrete_addresses
3273
+ ],
3274
+ "address",
3275
+ )
3267
3276
 
3268
- def validate_concept(self, lookup: str, meta: Meta | None = None):
3277
+ def validate_concept(self, new_concept: Concept, meta: Meta | None = None):
3278
+ lookup = new_concept.address
3269
3279
  existing: Concept = self.concepts.get(lookup) # type: ignore
3270
3280
  if not existing:
3271
3281
  return
3272
- elif existing and self.environment_config.allow_duplicate_declaration:
3282
+
3283
+ def handle_persist():
3284
+ deriv_lookup = (
3285
+ f"{existing.namespace}.{PERSISTED_CONCEPT_PREFIX}_{existing.name}"
3286
+ )
3287
+
3288
+ alt_source = self.alias_origin_lookup.get(deriv_lookup)
3289
+ if not alt_source:
3290
+ return None
3291
+ # if the new concept binding has no lineage
3292
+ # nothing to cause us to think a persist binding
3293
+ # needs to be invalidated
3294
+ if not new_concept.lineage:
3295
+ return existing
3296
+ if str(alt_source.lineage) == str(new_concept.lineage):
3297
+ logger.info(
3298
+ f"Persisted concept {existing.address} matched redeclaration, keeping current persistence binding."
3299
+ )
3300
+ return existing
3301
+ logger.warning(
3302
+ f"Persisted concept {existing.address} lineage {str(alt_source.lineage)} did not match redeclaration {str(new_concept.lineage)}, overwriting and invalidating persist binding."
3303
+ )
3304
+ for k, datasource in self.datasources.items():
3305
+ if existing.address in datasource.output_concepts:
3306
+ datasource.columns = [
3307
+ x
3308
+ for x in datasource.columns
3309
+ if x.concept.address != existing.address
3310
+ ]
3311
+ return None
3312
+
3313
+ if existing and self.environment_config.allow_duplicate_declaration:
3314
+ if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
3315
+ return handle_persist()
3273
3316
  return
3274
3317
  elif existing.metadata:
3318
+ if existing.metadata.concept_source == ConceptSource.PERSIST_STATEMENT:
3319
+ return handle_persist()
3275
3320
  # if the existing concept is auto derived, we can overwrite it
3276
3321
  if existing.metadata.concept_source == ConceptSource.AUTO_DERIVED:
3277
- return
3322
+ return None
3278
3323
  elif meta and existing.metadata:
3279
3324
  raise ValueError(
3280
3325
  f"Assignment to concept '{lookup}' on line {meta.line} is a duplicate"
@@ -3390,11 +3435,12 @@ class Environment(BaseModel):
3390
3435
  _ignore_cache: bool = False,
3391
3436
  ):
3392
3437
  if not force:
3393
- self.validate_concept(concept.address, meta=meta)
3438
+ existing = self.validate_concept(concept, meta=meta)
3439
+ if existing:
3440
+ concept = existing
3394
3441
  if concept.namespace == DEFAULT_NAMESPACE:
3395
3442
  self.concepts[concept.name] = concept
3396
- else:
3397
- self.concepts[concept.address] = concept
3443
+ self.concepts[concept.address] = concept
3398
3444
  from trilogy.core.environment_helpers import generate_related_concepts
3399
3445
 
3400
3446
  generate_related_concepts(concept, self, meta=meta, add_derived=add_derived)
@@ -3408,21 +3454,42 @@ class Environment(BaseModel):
3408
3454
  meta: Meta | None = None,
3409
3455
  _ignore_cache: bool = False,
3410
3456
  ):
3411
-
3412
3457
  self.datasources[datasource.env_label] = datasource
3413
- for column in datasource.columns:
3414
- current_concept = column.concept
3458
+ for current_concept in datasource.output_concepts:
3415
3459
  current_derivation = current_concept.derivation
3460
+ # TODO: refine this section;
3461
+ # too hacky for maintainability
3416
3462
  if current_derivation not in (PurposeLineage.ROOT, PurposeLineage.CONSTANT):
3417
- new_concept = current_concept.model_copy(deep=True)
3418
- new_concept.set_name("_pre_persist_" + current_concept.name)
3419
- # remove the associated lineage
3420
- current_concept.lineage = None
3421
- self.add_concept(new_concept, meta=meta, force=True, _ignore_cache=True)
3422
- self.add_concept(
3423
- current_concept, meta=meta, force=True, _ignore_cache=True
3424
- )
3425
- self.merge_concept(new_concept, current_concept, [])
3463
+ persisted = f"{PERSISTED_CONCEPT_PREFIX}_" + current_concept.name
3464
+ # override the current concept source to reflect that it's now coming from a datasource
3465
+ if (
3466
+ current_concept.metadata.concept_source
3467
+ != ConceptSource.PERSIST_STATEMENT
3468
+ ):
3469
+ new_concept = current_concept.model_copy(deep=True)
3470
+ new_concept.set_name(persisted)
3471
+ self.add_concept(
3472
+ new_concept, meta=meta, force=True, _ignore_cache=True
3473
+ )
3474
+ current_concept.metadata.concept_source = (
3475
+ ConceptSource.PERSIST_STATEMENT
3476
+ )
3477
+ # remove the associated lineage
3478
+ # to make this a root for discovery purposes
3479
+ # as it now "exists" in a table
3480
+ current_concept.lineage = None
3481
+ current_concept = current_concept.with_default_grain()
3482
+ self.add_concept(
3483
+ current_concept, meta=meta, force=True, _ignore_cache=True
3484
+ )
3485
+ self.merge_concept(new_concept, current_concept, [])
3486
+ else:
3487
+ self.add_concept(current_concept, meta=meta, _ignore_cache=True)
3488
+
3489
+ # else:
3490
+ # self.add_concept(
3491
+ # current_concept, meta=meta, _ignore_cache=True
3492
+ # )
3426
3493
  if not _ignore_cache:
3427
3494
  self.gen_concept_list_caches()
3428
3495
  return datasource
@@ -4230,6 +4297,9 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
4230
4297
  def __repr__(self):
4231
4298
  return f"RowsetDerivation<{str(self.select)}>"
4232
4299
 
4300
+ def __str__(self):
4301
+ return self.__repr__()
4302
+
4233
4303
  @property
4234
4304
  def derived_concepts(self) -> List[Concept]:
4235
4305
  output: list[Concept] = []
@@ -4248,7 +4318,8 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
4248
4318
  content=orig_concept, where=self.select.where_clause, rowset=self
4249
4319
  ),
4250
4320
  grain=orig_concept.grain,
4251
- metadata=orig_concept.metadata,
4321
+ # TODO: add proper metadata
4322
+ metadata=Metadata(concept_source=ConceptSource.CTE),
4252
4323
  namespace=(
4253
4324
  f"{self.name}.{orig_concept.namespace}"
4254
4325
  if orig_concept.namespace != self.namespace
@@ -4275,6 +4346,7 @@ class RowsetDerivationStatement(Namespaced, BaseModel):
4275
4346
  components=[orig[c.address] for c in x.grain.components_copy]
4276
4347
  )
4277
4348
  else:
4349
+
4278
4350
  x.grain = default_grain
4279
4351
  return output
4280
4352
 
@@ -4300,6 +4372,9 @@ class RowsetItem(Mergeable, Namespaced, BaseModel):
4300
4372
  f"<Rowset<{self.rowset.name}>: {str(self.content)} where {str(self.where)}>"
4301
4373
  )
4302
4374
 
4375
+ def __str__(self):
4376
+ return self.__repr__()
4377
+
4303
4378
  def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
4304
4379
  return RowsetItem(
4305
4380
  content=self.content.with_merge(source, target, modifiers),
@@ -432,7 +432,6 @@ def generate_node(
432
432
  conditions=conditions,
433
433
  )
434
434
  if not check:
435
-
436
435
  logger.info(
437
436
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Could not resolve root concepts, checking for expanded concepts"
438
437
  )