pytrilogy 0.0.2.52__tar.gz → 0.0.2.54__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.
- {pytrilogy-0.0.2.52/pytrilogy.egg-info → pytrilogy-0.0.2.54}/PKG-INFO +32 -12
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/README.md +31 -11
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54/pytrilogy.egg-info}/PKG-INFO +32 -12
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_parsing.py +5 -5
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_partial_handling.py +15 -5
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/environment_helpers.py +9 -3
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/models.py +27 -55
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/common.py +4 -3
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/filter_node.py +3 -2
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/rowset_node.py +0 -1
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/common.py +14 -12
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/parse_engine.py +22 -19
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/render.py +8 -6
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/LICENSE.md +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/pyproject.toml +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/pytrilogy.egg-info/SOURCES.txt +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/setup.cfg +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/setup.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_functions.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_models.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_select.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_show.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/window_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/base.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/bigquery.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/parsing/trilogy.lark +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/utility.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.54
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -34,7 +34,7 @@ Requires-Dist: snowflake-sqlalchemy; extra == "snowflake"
|
|
|
34
34
|
|
|
35
35
|
pytrilogy is an experimental implementation of the Trilogy language, a higher-level SQL that replaces tables/joins with a lightweight semantic binding layer.
|
|
36
36
|
|
|
37
|
-
Trilogy looks like SQL, but simpler. It's a modern SQL refresh targeted at SQL lovers who want reusability and
|
|
37
|
+
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.
|
|
38
38
|
|
|
39
39
|
> [!TIP]
|
|
40
40
|
> To get an overview of the language and run interactive examples, head to the [documentation](https://trilogydata.dev/).
|
|
@@ -193,11 +193,12 @@ address `bigquery-public-data.usa_names.usa_1910_2013`;
|
|
|
193
193
|
executor = Dialects.BIGQUERY.default_executor(environment=environment)
|
|
194
194
|
|
|
195
195
|
results = executor.execute_text(
|
|
196
|
-
'''
|
|
197
|
-
name,
|
|
198
|
-
sum(yearly_name_count) -> name_count
|
|
196
|
+
'''
|
|
199
197
|
WHERE
|
|
200
198
|
name = 'Elvis'
|
|
199
|
+
SELECT
|
|
200
|
+
name,
|
|
201
|
+
sum(yearly_name_count) -> name_count
|
|
201
202
|
ORDER BY
|
|
202
203
|
name_count desc
|
|
203
204
|
LIMIT 10;
|
|
@@ -270,9 +271,11 @@ Please open an issue first to discuss what you would like to change, and then cr
|
|
|
270
271
|
## Similar in space
|
|
271
272
|
Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
|
|
272
273
|
|
|
273
|
-
|
|
274
|
+
"semantic layers" are tools for defining an metadata layer above a SQL/warehouse base to enable higher level abstractions.
|
|
274
275
|
|
|
275
276
|
- [metricsflow](https://github.com/dbt-labs/metricflow)
|
|
277
|
+
- [cube](https://github.com/cube-js/cube)
|
|
278
|
+
- [zillion](https://github.com/totalhack/zillion)
|
|
276
279
|
|
|
277
280
|
"Better SQL" has been a popular space. We believe Trilogy takes a different approach then the following,
|
|
278
281
|
but all are worth checking out. Please open PRs/comment for anything missed!
|
|
@@ -321,11 +324,14 @@ address <table>;
|
|
|
321
324
|
Primary acces
|
|
322
325
|
|
|
323
326
|
```sql
|
|
324
|
-
select
|
|
325
|
-
<concept>,
|
|
326
|
-
<concept>+1 -> <alias>
|
|
327
327
|
WHERE
|
|
328
328
|
<concept> = <value>
|
|
329
|
+
select
|
|
330
|
+
<concept>,
|
|
331
|
+
<concept>+1 -> <alias>,
|
|
332
|
+
...
|
|
333
|
+
HAVING
|
|
334
|
+
<alias> = <value2>
|
|
329
335
|
ORDER BY
|
|
330
336
|
<concept> asc|desc
|
|
331
337
|
;
|
|
@@ -337,11 +343,13 @@ Reusable virtual set of rows. Useful for windows, filtering.
|
|
|
337
343
|
|
|
338
344
|
```sql
|
|
339
345
|
with <alias> as
|
|
340
|
-
select
|
|
341
|
-
<concept>,
|
|
342
|
-
<concept>+1 -> <alias>
|
|
343
346
|
WHERE
|
|
344
347
|
<concept> = <value>
|
|
348
|
+
select
|
|
349
|
+
<concept>,
|
|
350
|
+
<concept>+1 -> <alias>,
|
|
351
|
+
...
|
|
352
|
+
|
|
345
353
|
|
|
346
354
|
select <alias>.<concept>;
|
|
347
355
|
|
|
@@ -357,6 +365,18 @@ persist <alias> as <table_name> from
|
|
|
357
365
|
<select>;
|
|
358
366
|
```
|
|
359
367
|
|
|
368
|
+
#### COPY
|
|
369
|
+
|
|
370
|
+
Currently supported target types are <CSV>, though backend support may vary.
|
|
371
|
+
|
|
372
|
+
```sql
|
|
373
|
+
COPY INTO <TARGET_TYPE> '<target_path>' FROM SELECT
|
|
374
|
+
<concept>, ...
|
|
375
|
+
ORDER BY
|
|
376
|
+
<concept>, ...
|
|
377
|
+
;
|
|
378
|
+
```
|
|
379
|
+
|
|
360
380
|
#### SHOW
|
|
361
381
|
|
|
362
382
|
Return generated SQL without executing.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
pytrilogy is an experimental implementation of the Trilogy language, a higher-level SQL that replaces tables/joins with a lightweight semantic binding layer.
|
|
6
6
|
|
|
7
|
-
Trilogy looks like SQL, but simpler. It's a modern SQL refresh targeted at SQL lovers who want reusability and
|
|
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.
|
|
8
8
|
|
|
9
9
|
> [!TIP]
|
|
10
10
|
> To get an overview of the language and run interactive examples, head to the [documentation](https://trilogydata.dev/).
|
|
@@ -163,11 +163,12 @@ address `bigquery-public-data.usa_names.usa_1910_2013`;
|
|
|
163
163
|
executor = Dialects.BIGQUERY.default_executor(environment=environment)
|
|
164
164
|
|
|
165
165
|
results = executor.execute_text(
|
|
166
|
-
'''
|
|
167
|
-
name,
|
|
168
|
-
sum(yearly_name_count) -> name_count
|
|
166
|
+
'''
|
|
169
167
|
WHERE
|
|
170
168
|
name = 'Elvis'
|
|
169
|
+
SELECT
|
|
170
|
+
name,
|
|
171
|
+
sum(yearly_name_count) -> name_count
|
|
171
172
|
ORDER BY
|
|
172
173
|
name_count desc
|
|
173
174
|
LIMIT 10;
|
|
@@ -240,9 +241,11 @@ Please open an issue first to discuss what you would like to change, and then cr
|
|
|
240
241
|
## Similar in space
|
|
241
242
|
Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
|
|
242
243
|
|
|
243
|
-
|
|
244
|
+
"semantic layers" are tools for defining an metadata layer above a SQL/warehouse base to enable higher level abstractions.
|
|
244
245
|
|
|
245
246
|
- [metricsflow](https://github.com/dbt-labs/metricflow)
|
|
247
|
+
- [cube](https://github.com/cube-js/cube)
|
|
248
|
+
- [zillion](https://github.com/totalhack/zillion)
|
|
246
249
|
|
|
247
250
|
"Better SQL" has been a popular space. We believe Trilogy takes a different approach then the following,
|
|
248
251
|
but all are worth checking out. Please open PRs/comment for anything missed!
|
|
@@ -291,11 +294,14 @@ address <table>;
|
|
|
291
294
|
Primary acces
|
|
292
295
|
|
|
293
296
|
```sql
|
|
294
|
-
select
|
|
295
|
-
<concept>,
|
|
296
|
-
<concept>+1 -> <alias>
|
|
297
297
|
WHERE
|
|
298
298
|
<concept> = <value>
|
|
299
|
+
select
|
|
300
|
+
<concept>,
|
|
301
|
+
<concept>+1 -> <alias>,
|
|
302
|
+
...
|
|
303
|
+
HAVING
|
|
304
|
+
<alias> = <value2>
|
|
299
305
|
ORDER BY
|
|
300
306
|
<concept> asc|desc
|
|
301
307
|
;
|
|
@@ -307,11 +313,13 @@ Reusable virtual set of rows. Useful for windows, filtering.
|
|
|
307
313
|
|
|
308
314
|
```sql
|
|
309
315
|
with <alias> as
|
|
310
|
-
select
|
|
311
|
-
<concept>,
|
|
312
|
-
<concept>+1 -> <alias>
|
|
313
316
|
WHERE
|
|
314
317
|
<concept> = <value>
|
|
318
|
+
select
|
|
319
|
+
<concept>,
|
|
320
|
+
<concept>+1 -> <alias>,
|
|
321
|
+
...
|
|
322
|
+
|
|
315
323
|
|
|
316
324
|
select <alias>.<concept>;
|
|
317
325
|
|
|
@@ -327,6 +335,18 @@ persist <alias> as <table_name> from
|
|
|
327
335
|
<select>;
|
|
328
336
|
```
|
|
329
337
|
|
|
338
|
+
#### COPY
|
|
339
|
+
|
|
340
|
+
Currently supported target types are <CSV>, though backend support may vary.
|
|
341
|
+
|
|
342
|
+
```sql
|
|
343
|
+
COPY INTO <TARGET_TYPE> '<target_path>' FROM SELECT
|
|
344
|
+
<concept>, ...
|
|
345
|
+
ORDER BY
|
|
346
|
+
<concept>, ...
|
|
347
|
+
;
|
|
348
|
+
```
|
|
349
|
+
|
|
330
350
|
#### SHOW
|
|
331
351
|
|
|
332
352
|
Return generated SQL without executing.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.54
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -34,7 +34,7 @@ Requires-Dist: snowflake-sqlalchemy; extra == "snowflake"
|
|
|
34
34
|
|
|
35
35
|
pytrilogy is an experimental implementation of the Trilogy language, a higher-level SQL that replaces tables/joins with a lightweight semantic binding layer.
|
|
36
36
|
|
|
37
|
-
Trilogy looks like SQL, but simpler. It's a modern SQL refresh targeted at SQL lovers who want reusability and
|
|
37
|
+
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.
|
|
38
38
|
|
|
39
39
|
> [!TIP]
|
|
40
40
|
> To get an overview of the language and run interactive examples, head to the [documentation](https://trilogydata.dev/).
|
|
@@ -193,11 +193,12 @@ address `bigquery-public-data.usa_names.usa_1910_2013`;
|
|
|
193
193
|
executor = Dialects.BIGQUERY.default_executor(environment=environment)
|
|
194
194
|
|
|
195
195
|
results = executor.execute_text(
|
|
196
|
-
'''
|
|
197
|
-
name,
|
|
198
|
-
sum(yearly_name_count) -> name_count
|
|
196
|
+
'''
|
|
199
197
|
WHERE
|
|
200
198
|
name = 'Elvis'
|
|
199
|
+
SELECT
|
|
200
|
+
name,
|
|
201
|
+
sum(yearly_name_count) -> name_count
|
|
201
202
|
ORDER BY
|
|
202
203
|
name_count desc
|
|
203
204
|
LIMIT 10;
|
|
@@ -270,9 +271,11 @@ Please open an issue first to discuss what you would like to change, and then cr
|
|
|
270
271
|
## Similar in space
|
|
271
272
|
Trilogy combines two aspects; a semantic layer and a query language. Examples of both are linked below:
|
|
272
273
|
|
|
273
|
-
|
|
274
|
+
"semantic layers" are tools for defining an metadata layer above a SQL/warehouse base to enable higher level abstractions.
|
|
274
275
|
|
|
275
276
|
- [metricsflow](https://github.com/dbt-labs/metricflow)
|
|
277
|
+
- [cube](https://github.com/cube-js/cube)
|
|
278
|
+
- [zillion](https://github.com/totalhack/zillion)
|
|
276
279
|
|
|
277
280
|
"Better SQL" has been a popular space. We believe Trilogy takes a different approach then the following,
|
|
278
281
|
but all are worth checking out. Please open PRs/comment for anything missed!
|
|
@@ -321,11 +324,14 @@ address <table>;
|
|
|
321
324
|
Primary acces
|
|
322
325
|
|
|
323
326
|
```sql
|
|
324
|
-
select
|
|
325
|
-
<concept>,
|
|
326
|
-
<concept>+1 -> <alias>
|
|
327
327
|
WHERE
|
|
328
328
|
<concept> = <value>
|
|
329
|
+
select
|
|
330
|
+
<concept>,
|
|
331
|
+
<concept>+1 -> <alias>,
|
|
332
|
+
...
|
|
333
|
+
HAVING
|
|
334
|
+
<alias> = <value2>
|
|
329
335
|
ORDER BY
|
|
330
336
|
<concept> asc|desc
|
|
331
337
|
;
|
|
@@ -337,11 +343,13 @@ Reusable virtual set of rows. Useful for windows, filtering.
|
|
|
337
343
|
|
|
338
344
|
```sql
|
|
339
345
|
with <alias> as
|
|
340
|
-
select
|
|
341
|
-
<concept>,
|
|
342
|
-
<concept>+1 -> <alias>
|
|
343
346
|
WHERE
|
|
344
347
|
<concept> = <value>
|
|
348
|
+
select
|
|
349
|
+
<concept>,
|
|
350
|
+
<concept>+1 -> <alias>,
|
|
351
|
+
...
|
|
352
|
+
|
|
345
353
|
|
|
346
354
|
select <alias>.<concept>;
|
|
347
355
|
|
|
@@ -357,6 +365,18 @@ persist <alias> as <table_name> from
|
|
|
357
365
|
<select>;
|
|
358
366
|
```
|
|
359
367
|
|
|
368
|
+
#### COPY
|
|
369
|
+
|
|
370
|
+
Currently supported target types are <CSV>, though backend support may vary.
|
|
371
|
+
|
|
372
|
+
```sql
|
|
373
|
+
COPY INTO <TARGET_TYPE> '<target_path>' FROM SELECT
|
|
374
|
+
<concept>, ...
|
|
375
|
+
ORDER BY
|
|
376
|
+
<concept>, ...
|
|
377
|
+
;
|
|
378
|
+
```
|
|
379
|
+
|
|
360
380
|
#### SHOW
|
|
361
381
|
|
|
362
382
|
Return generated SQL without executing.
|
|
@@ -182,7 +182,7 @@ select
|
|
|
182
182
|
]:
|
|
183
183
|
assert f"local.{name}" in env.concepts
|
|
184
184
|
assert env.concepts[name].purpose == Purpose.PROPERTY
|
|
185
|
-
assert env.concepts[name].keys ==
|
|
185
|
+
assert env.concepts[name].keys == {env.concepts["id"].address}
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
def test_purpose_and_derivation():
|
|
@@ -200,10 +200,10 @@ select
|
|
|
200
200
|
|
|
201
201
|
for name in ["join_id"]:
|
|
202
202
|
assert env.concepts[name].purpose == Purpose.PROPERTY
|
|
203
|
-
assert env.concepts[name].keys ==
|
|
204
|
-
env.concepts["id"],
|
|
205
|
-
env.concepts["other_id"],
|
|
206
|
-
|
|
203
|
+
assert env.concepts[name].keys == {
|
|
204
|
+
env.concepts["id"].address,
|
|
205
|
+
env.concepts["other_id"].address,
|
|
206
|
+
}
|
|
207
207
|
|
|
208
208
|
|
|
209
209
|
def test_output_purpose():
|
|
@@ -35,7 +35,9 @@ def setup_titanic(env: Environment):
|
|
|
35
35
|
namespace=namespace,
|
|
36
36
|
datatype=DataType.INTEGER,
|
|
37
37
|
purpose=Purpose.PROPERTY,
|
|
38
|
-
keys=
|
|
38
|
+
keys={
|
|
39
|
+
id.address,
|
|
40
|
+
},
|
|
39
41
|
)
|
|
40
42
|
|
|
41
43
|
name = Concept(
|
|
@@ -43,7 +45,9 @@ def setup_titanic(env: Environment):
|
|
|
43
45
|
namespace=namespace,
|
|
44
46
|
datatype=DataType.STRING,
|
|
45
47
|
purpose=Purpose.PROPERTY,
|
|
46
|
-
keys=
|
|
48
|
+
keys={
|
|
49
|
+
id.address,
|
|
50
|
+
},
|
|
47
51
|
)
|
|
48
52
|
|
|
49
53
|
pclass = Concept(
|
|
@@ -51,21 +55,27 @@ def setup_titanic(env: Environment):
|
|
|
51
55
|
namespace=namespace,
|
|
52
56
|
purpose=Purpose.PROPERTY,
|
|
53
57
|
datatype=DataType.INTEGER,
|
|
54
|
-
keys=
|
|
58
|
+
keys={
|
|
59
|
+
id.address,
|
|
60
|
+
},
|
|
55
61
|
)
|
|
56
62
|
survived = Concept(
|
|
57
63
|
name="survived",
|
|
58
64
|
namespace=namespace,
|
|
59
65
|
purpose=Purpose.PROPERTY,
|
|
60
66
|
datatype=DataType.INTEGER,
|
|
61
|
-
keys=
|
|
67
|
+
keys={
|
|
68
|
+
id.address,
|
|
69
|
+
},
|
|
62
70
|
)
|
|
63
71
|
fare = Concept(
|
|
64
72
|
name="fare",
|
|
65
73
|
namespace=namespace,
|
|
66
74
|
purpose=Purpose.PROPERTY,
|
|
67
75
|
datatype=DataType.FLOAT,
|
|
68
|
-
keys=
|
|
76
|
+
keys={
|
|
77
|
+
id.address,
|
|
78
|
+
},
|
|
69
79
|
)
|
|
70
80
|
for x in [id, age, survived, name, pclass, fare]:
|
|
71
81
|
env.add_concept(x)
|
|
@@ -50,7 +50,9 @@ def generate_date_concepts(concept: Concept, environment: Environment):
|
|
|
50
50
|
lineage=const_function,
|
|
51
51
|
grain=const_function.output_grain,
|
|
52
52
|
namespace=namespace,
|
|
53
|
-
keys=(
|
|
53
|
+
keys=set(
|
|
54
|
+
concept.address,
|
|
55
|
+
),
|
|
54
56
|
metadata=Metadata(
|
|
55
57
|
description=f"Auto-derived. Integer format. The {ftype.value} derived from {concept.name}, {base_description}",
|
|
56
58
|
line_number=base_line_number,
|
|
@@ -99,7 +101,9 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
|
|
|
99
101
|
lineage=const_function,
|
|
100
102
|
grain=const_function.output_grain,
|
|
101
103
|
namespace=namespace,
|
|
102
|
-
keys=(
|
|
104
|
+
keys=set(
|
|
105
|
+
concept.address,
|
|
106
|
+
),
|
|
103
107
|
metadata=Metadata(
|
|
104
108
|
description=f"Auto-derived. Integer format. The {ftype.value} derived from {concept.name}, {base_description}",
|
|
105
109
|
line_number=base_line_number,
|
|
@@ -139,7 +143,9 @@ def generate_key_concepts(concept: Concept, environment: Environment):
|
|
|
139
143
|
lineage=const_function,
|
|
140
144
|
grain=const_function.output_grain,
|
|
141
145
|
namespace=namespace,
|
|
142
|
-
keys=
|
|
146
|
+
keys={
|
|
147
|
+
concept.address,
|
|
148
|
+
},
|
|
143
149
|
metadata=Metadata(
|
|
144
150
|
description=f"Auto-derived. Integer format. The {ftype.value} derived from {concept.name}, {base_description}",
|
|
145
151
|
line_number=base_line_number,
|
|
@@ -427,12 +427,15 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
427
427
|
]
|
|
428
428
|
] = None
|
|
429
429
|
namespace: Optional[str] = Field(default=DEFAULT_NAMESPACE, validate_default=True)
|
|
430
|
-
keys: Optional[
|
|
430
|
+
keys: Optional[set[str]] = None
|
|
431
431
|
grain: "Grain" = Field(default=None, validate_default=True) # type: ignore
|
|
432
432
|
modifiers: List[Modifier] = Field(default_factory=list) # type: ignore
|
|
433
433
|
pseudonyms: set[str] = Field(default_factory=set)
|
|
434
434
|
_address_cache: str | None = None
|
|
435
435
|
|
|
436
|
+
def __init__(self, **data):
|
|
437
|
+
super().__init__(**data)
|
|
438
|
+
|
|
436
439
|
def duplicate(self) -> Concept:
|
|
437
440
|
return self.model_copy(deep=True)
|
|
438
441
|
|
|
@@ -480,7 +483,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
480
483
|
grain=self.grain.with_merge(source, target, modifiers),
|
|
481
484
|
namespace=self.namespace,
|
|
482
485
|
keys=(
|
|
483
|
-
|
|
486
|
+
set(x if x != source.address else target.address for x in self.keys)
|
|
484
487
|
if self.keys
|
|
485
488
|
else None
|
|
486
489
|
),
|
|
@@ -488,17 +491,6 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
488
491
|
pseudonyms=self.pseudonyms,
|
|
489
492
|
)
|
|
490
493
|
|
|
491
|
-
@field_validator("keys", mode="before")
|
|
492
|
-
@classmethod
|
|
493
|
-
def keys_validator(cls, v, info: ValidationInfo):
|
|
494
|
-
if v is None:
|
|
495
|
-
return v
|
|
496
|
-
if not isinstance(v, (list, tuple)):
|
|
497
|
-
raise ValueError(f"Keys must be a list or tuple, got {type(v)}")
|
|
498
|
-
if isinstance(v, list):
|
|
499
|
-
return tuple(v)
|
|
500
|
-
return v
|
|
501
|
-
|
|
502
494
|
@field_validator("namespace", mode="plain")
|
|
503
495
|
@classmethod
|
|
504
496
|
def namespace_validation(cls, v):
|
|
@@ -610,7 +602,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
610
602
|
else namespace
|
|
611
603
|
),
|
|
612
604
|
keys=(
|
|
613
|
-
|
|
605
|
+
set([address_with_namespace(x, namespace) for x in self.keys])
|
|
614
606
|
if self.keys
|
|
615
607
|
else None
|
|
616
608
|
),
|
|
@@ -627,25 +619,17 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
627
619
|
local_concepts=local_concepts, grain=grain, environment=environment
|
|
628
620
|
)
|
|
629
621
|
final_grain = self.grain or grain
|
|
630
|
-
keys =
|
|
631
|
-
tuple(
|
|
632
|
-
[
|
|
633
|
-
x.with_select_context(local_concepts, grain, environment)
|
|
634
|
-
for x in self.keys
|
|
635
|
-
]
|
|
636
|
-
)
|
|
637
|
-
if self.keys
|
|
638
|
-
else None
|
|
639
|
-
)
|
|
622
|
+
keys = self.keys if self.keys else None
|
|
640
623
|
if self.is_aggregate and isinstance(new_lineage, Function):
|
|
641
624
|
grain_components = [environment.concepts[c] for c in grain.components]
|
|
642
625
|
new_lineage = AggregateWrapper(function=new_lineage, by=grain_components)
|
|
643
626
|
final_grain = grain
|
|
644
|
-
keys =
|
|
627
|
+
keys = set(grain.components)
|
|
645
628
|
elif (
|
|
646
629
|
self.is_aggregate and not keys and isinstance(new_lineage, AggregateWrapper)
|
|
647
630
|
):
|
|
648
|
-
keys =
|
|
631
|
+
keys = set([x.address for x in new_lineage.by])
|
|
632
|
+
|
|
649
633
|
return self.__class__(
|
|
650
634
|
name=self.name,
|
|
651
635
|
datatype=self.datatype,
|
|
@@ -687,13 +671,10 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
687
671
|
if self.lineage:
|
|
688
672
|
for item in self.lineage.arguments:
|
|
689
673
|
if isinstance(item, Concept):
|
|
690
|
-
|
|
691
|
-
components += item.sources
|
|
692
|
-
else:
|
|
693
|
-
components += item.sources
|
|
674
|
+
components += [x.address for x in item.sources]
|
|
694
675
|
# TODO: set synonyms
|
|
695
676
|
grain = Grain(
|
|
696
|
-
components=set([x
|
|
677
|
+
components=set([x for x in components]),
|
|
697
678
|
) # synonym_set=generate_concept_synonyms(components))
|
|
698
679
|
elif self.purpose == Purpose.METRIC:
|
|
699
680
|
grain = Grain()
|
|
@@ -1058,13 +1039,19 @@ class EnvironmentConceptDict(dict):
|
|
|
1058
1039
|
if DEFAULT_NAMESPACE + "." + key in self:
|
|
1059
1040
|
return self.__getitem__(DEFAULT_NAMESPACE + "." + key, line_no)
|
|
1060
1041
|
if not self.fail_on_missing:
|
|
1042
|
+
if "." in key:
|
|
1043
|
+
ns, rest = key.rsplit(".", 1)
|
|
1044
|
+
else:
|
|
1045
|
+
ns = DEFAULT_NAMESPACE
|
|
1046
|
+
rest = key
|
|
1061
1047
|
if key in self.undefined:
|
|
1062
1048
|
return self.undefined[key]
|
|
1063
1049
|
undefined = UndefinedConcept(
|
|
1064
|
-
name=
|
|
1050
|
+
name=rest,
|
|
1065
1051
|
line_no=line_no,
|
|
1066
1052
|
datatype=DataType.UNKNOWN,
|
|
1067
1053
|
purpose=Purpose.UNKNOWN,
|
|
1054
|
+
namespace=ns,
|
|
1068
1055
|
)
|
|
1069
1056
|
self.undefined[key] = undefined
|
|
1070
1057
|
return undefined
|
|
@@ -1365,18 +1352,6 @@ class Function(Mergeable, Namespaced, SelectContext, BaseModel):
|
|
|
1365
1352
|
base_grain += input.grain
|
|
1366
1353
|
return base_grain
|
|
1367
1354
|
|
|
1368
|
-
@property
|
|
1369
|
-
def output_keys(self) -> list[Concept]:
|
|
1370
|
-
# aggregates have an abstract grain
|
|
1371
|
-
components = []
|
|
1372
|
-
# scalars have implicit grain of all arguments
|
|
1373
|
-
for input in self.concept_arguments:
|
|
1374
|
-
if input.purpose == Purpose.KEY:
|
|
1375
|
-
components.append(input)
|
|
1376
|
-
elif input.keys:
|
|
1377
|
-
components += input.keys
|
|
1378
|
-
return list(set(components))
|
|
1379
|
-
|
|
1380
1355
|
|
|
1381
1356
|
class ConceptTransform(Namespaced, BaseModel):
|
|
1382
1357
|
function: Function | FilterItem | WindowItem | AggregateWrapper
|
|
@@ -3203,7 +3178,9 @@ class UndefinedConcept(Concept, Mergeable, Namespaced):
|
|
|
3203
3178
|
rval = local_concepts[self.address]
|
|
3204
3179
|
rval = rval.with_select_context(local_concepts, grain, environment)
|
|
3205
3180
|
return rval
|
|
3206
|
-
environment.concepts.
|
|
3181
|
+
if environment.concepts.fail_on_missing:
|
|
3182
|
+
environment.concepts.raise_undefined(self.address, line_no=self.line_no)
|
|
3183
|
+
return self
|
|
3207
3184
|
|
|
3208
3185
|
|
|
3209
3186
|
class EnvironmentDatasourceDict(dict):
|
|
@@ -3336,6 +3313,8 @@ class Environment(BaseModel):
|
|
|
3336
3313
|
|
|
3337
3314
|
@classmethod
|
|
3338
3315
|
def from_file(cls, path: str | Path) -> "Environment":
|
|
3316
|
+
if isinstance(path, str):
|
|
3317
|
+
path = Path(path)
|
|
3339
3318
|
with open(path, "r") as f:
|
|
3340
3319
|
read = f.read()
|
|
3341
3320
|
return Environment(working_path=Path(path).parent).parse(read)[0]
|
|
@@ -3645,11 +3624,6 @@ class Environment(BaseModel):
|
|
|
3645
3624
|
self.merge_concept(new_concept, current_concept, [])
|
|
3646
3625
|
else:
|
|
3647
3626
|
self.add_concept(current_concept, meta=meta, _ignore_cache=True)
|
|
3648
|
-
|
|
3649
|
-
# else:
|
|
3650
|
-
# self.add_concept(
|
|
3651
|
-
# current_concept, meta=meta, _ignore_cache=True
|
|
3652
|
-
# )
|
|
3653
3627
|
if not _ignore_cache:
|
|
3654
3628
|
self.gen_concept_list_caches()
|
|
3655
3629
|
return datasource
|
|
@@ -4521,13 +4495,11 @@ class RowsetDerivationStatement(HasUUID, Namespaced, BaseModel):
|
|
|
4521
4495
|
# remap everything to the properties of the rowset
|
|
4522
4496
|
for x in output:
|
|
4523
4497
|
if x.keys:
|
|
4524
|
-
if all([k
|
|
4525
|
-
x.keys =
|
|
4526
|
-
[orig[k.address] if k.address in orig else k for k in x.keys]
|
|
4527
|
-
)
|
|
4498
|
+
if all([k in orig for k in x.keys]):
|
|
4499
|
+
x.keys = set([orig[k].address if k in orig else k for k in x.keys])
|
|
4528
4500
|
else:
|
|
4529
4501
|
# TODO: fix this up
|
|
4530
|
-
x.keys =
|
|
4502
|
+
x.keys = set()
|
|
4531
4503
|
for x in output:
|
|
4532
4504
|
if all([c in orig for c in x.grain.components]):
|
|
4533
4505
|
x.grain = Grain(
|
|
@@ -26,6 +26,7 @@ def resolve_function_parent_concepts(
|
|
|
26
26
|
if not isinstance(concept.lineage, (Function, AggregateWrapper)):
|
|
27
27
|
raise ValueError(f"Concept {concept} lineage is not function or aggregate")
|
|
28
28
|
if concept.derivation == PurposeLineage.AGGREGATE:
|
|
29
|
+
base: list[Concept] = []
|
|
29
30
|
if not concept.grain.abstract:
|
|
30
31
|
base = concept.lineage.concept_arguments + [
|
|
31
32
|
environment.concepts[c] for c in concept.grain.components
|
|
@@ -41,7 +42,7 @@ def resolve_function_parent_concepts(
|
|
|
41
42
|
extra_property_grain = concept.lineage.concept_arguments
|
|
42
43
|
for x in extra_property_grain:
|
|
43
44
|
if isinstance(x, Concept) and x.purpose == Purpose.PROPERTY and x.keys:
|
|
44
|
-
base += x.keys
|
|
45
|
+
base += [environment.concepts[c] for c in x.keys]
|
|
45
46
|
return unique(base, "address")
|
|
46
47
|
# TODO: handle basic lineage chains?
|
|
47
48
|
return unique(concept.lineage.concept_arguments, "address")
|
|
@@ -81,7 +82,7 @@ def resolve_filter_parent_concepts(
|
|
|
81
82
|
and direct_parent.purpose == Purpose.PROPERTY
|
|
82
83
|
and direct_parent.keys
|
|
83
84
|
):
|
|
84
|
-
base_rows += direct_parent.keys
|
|
85
|
+
base_rows += [environment.concepts[c] for c in direct_parent.keys]
|
|
85
86
|
if concept.lineage.where.existence_arguments:
|
|
86
87
|
return (
|
|
87
88
|
concept.lineage.content,
|
|
@@ -106,7 +107,7 @@ def gen_property_enrichment_node(
|
|
|
106
107
|
for x in extra_properties:
|
|
107
108
|
if not x.keys:
|
|
108
109
|
raise SyntaxError(f"Property {x.address} missing keys in lookup")
|
|
109
|
-
keys = "-".join([y
|
|
110
|
+
keys = "-".join([y for y in x.keys])
|
|
110
111
|
required_keys[keys].add(x.address)
|
|
111
112
|
final_nodes = []
|
|
112
113
|
for _k, vs in required_keys.items():
|
{pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/filter_node.py
RENAMED
|
@@ -138,7 +138,7 @@ def gen_filter_node(
|
|
|
138
138
|
)
|
|
139
139
|
parent.grain = Grain.from_concepts(
|
|
140
140
|
(
|
|
141
|
-
|
|
141
|
+
[environment.concepts[k] for k in immediate_parent.keys]
|
|
142
142
|
if immediate_parent.keys
|
|
143
143
|
else [immediate_parent]
|
|
144
144
|
)
|
|
@@ -146,7 +146,8 @@ def gen_filter_node(
|
|
|
146
146
|
x
|
|
147
147
|
for x in local_optional
|
|
148
148
|
if x.address in [y.address for y in parent.output_concepts]
|
|
149
|
-
]
|
|
149
|
+
],
|
|
150
|
+
environment=environment,
|
|
150
151
|
)
|
|
151
152
|
parent.rebuild_cache()
|
|
152
153
|
filter_node = parent
|
{pytrilogy-0.0.2.52 → pytrilogy-0.0.2.54}/trilogy/core/processing/node_generators/rowset_node.py
RENAMED
|
@@ -97,7 +97,6 @@ def gen_rowset_node(
|
|
|
97
97
|
possible_joins = concept_to_relevant_joins(
|
|
98
98
|
[x for x in node.output_concepts if x.derivation != PurposeLineage.ROWSET]
|
|
99
99
|
)
|
|
100
|
-
logger.info({x.address: x.keys for x in possible_joins})
|
|
101
100
|
if not possible_joins:
|
|
102
101
|
logger.info(
|
|
103
102
|
f"{padding(depth)}{LOGGER_PREFIX} no possible joins for rowset node to get {[x.address for x in local_optional]}; have {[x.address for x in node.output_concepts]}"
|