pytrilogy 0.0.1.105__tar.gz → 0.0.1.107__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 (101) hide show
  1. {pytrilogy-0.0.1.105/pytrilogy.egg-info → pytrilogy-0.0.1.107}/PKG-INFO +79 -1
  2. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/README.md +78 -0
  3. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107/pytrilogy.egg-info}/PKG-INFO +79 -1
  4. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/pytrilogy.egg-info/SOURCES.txt +1 -0
  5. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_select.py +2 -1
  6. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/__init__.py +3 -2
  7. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/constants.py +1 -0
  8. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/models.py +128 -31
  9. pytrilogy-0.0.1.107/trilogy/core/optimization.py +141 -0
  10. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/base_node.py +4 -2
  11. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/group_node.py +5 -2
  12. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/merge_node.py +13 -8
  13. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/query_processor.py +5 -2
  14. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/base.py +73 -51
  15. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/bigquery.py +6 -4
  16. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/common.py +8 -6
  17. pytrilogy-0.0.1.107/trilogy/dialect/config.py +123 -0
  18. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/duckdb.py +5 -4
  19. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/enums.py +40 -19
  20. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/postgres.py +4 -2
  21. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/presto.py +6 -4
  22. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/snowflake.py +6 -4
  23. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/sql_server.py +4 -1
  24. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/executor.py +18 -5
  25. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parsing/parse_engine.py +1 -1
  26. pytrilogy-0.0.1.105/trilogy/dialect/config.py +0 -55
  27. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/LICENSE.md +0 -0
  28. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/pyproject.toml +0 -0
  29. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/pytrilogy.egg-info/dependency_links.txt +0 -0
  30. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/pytrilogy.egg-info/entry_points.txt +0 -0
  31. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/pytrilogy.egg-info/requires.txt +0 -0
  32. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/pytrilogy.egg-info/top_level.txt +0 -0
  33. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/setup.cfg +0 -0
  34. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/setup.py +0 -0
  35. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_declarations.py +0 -0
  36. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_derived_concepts.py +0 -0
  37. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_discovery_nodes.py +0 -0
  38. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_environment.py +0 -0
  39. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_functions.py +0 -0
  40. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_imports.py +0 -0
  41. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_metadata.py +0 -0
  42. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_models.py +0 -0
  43. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_multi_join_assignments.py +0 -0
  44. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_parsing.py +0 -0
  45. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_partial_handling.py +0 -0
  46. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_query_processing.py +0 -0
  47. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_statements.py +0 -0
  48. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_undefined_concept.py +0 -0
  49. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/tests/test_where_clause.py +0 -0
  50. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/compiler.py +0 -0
  51. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/__init__.py +0 -0
  52. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/constants.py +0 -0
  53. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/enums.py +0 -0
  54. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/env_processor.py +0 -0
  55. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/environment_helpers.py +0 -0
  56. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/ergonomics.py +0 -0
  57. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/exceptions.py +0 -0
  58. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/functions.py +0 -0
  59. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/graph_models.py +0 -0
  60. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/internal.py +0 -0
  61. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/__init__.py +0 -0
  62. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  63. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/graph_utils.py +0 -0
  64. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/__init__.py +0 -0
  65. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  66. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/common.py +0 -0
  67. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/concept_merge.py +0 -0
  68. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  69. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/group_node.py +0 -0
  70. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  71. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/merge_node.py +0 -0
  72. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  73. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  74. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/select_node.py +0 -0
  75. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  76. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/node_generators/window_node.py +0 -0
  77. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/__init__.py +0 -0
  78. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/filter_node.py +0 -0
  79. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  80. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  81. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/nodes/window_node.py +0 -0
  82. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/core/processing/utility.py +0 -0
  83. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/dialect/__init__.py +0 -0
  84. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/docs/__init__.py +0 -0
  85. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/engine.py +0 -0
  86. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/hooks/__init__.py +0 -0
  87. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/hooks/base_hook.py +0 -0
  88. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/hooks/graph_hook.py +0 -0
  89. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/hooks/query_debugger.py +0 -0
  90. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/metadata/__init__.py +0 -0
  91. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parser.py +0 -0
  92. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parsing/__init__.py +0 -0
  93. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parsing/common.py +0 -0
  94. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parsing/config.py +0 -0
  95. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parsing/exceptions.py +0 -0
  96. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parsing/helpers.py +0 -0
  97. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/parsing/render.py +0 -0
  98. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/py.typed +0 -0
  99. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/scripts/__init__.py +0 -0
  100. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/scripts/trilogy.py +0 -0
  101. {pytrilogy-0.0.1.105 → pytrilogy-0.0.1.107}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.105
3
+ Version: 0.0.1.107
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -275,3 +275,81 @@ but all are worth checking out. Please open PRs/comment for anything missed!
275
275
  - [malloy](https://github.com/malloydata/malloy)
276
276
  - [preql](https://github.com/erezsh/Preql)
277
277
  - [PREQL](https://github.com/PRQL/prql)
278
+
279
+ ## Minimal Syntax Reference
280
+
281
+ #### IMPORT
282
+
283
+ `import <path> as <alias>;`
284
+
285
+ #### CONCEPT
286
+
287
+ Types: `string | int | float | bool | date | datetime | time | timestamp | interval`;
288
+
289
+ Key:
290
+ `key <name> <type>;`
291
+
292
+ Property:
293
+ `property <key>.<name> <type>;`
294
+
295
+ Transformation:
296
+ `auto <name> <- <expression>;`
297
+
298
+ #### DATASOURCE
299
+ ```sql
300
+ datasource <name>(
301
+ <column>:<concept>,
302
+ <column>:<concept>,
303
+ )
304
+ grain(<concept>, <concept>)
305
+ address <table>;
306
+ ```
307
+
308
+ #### SELECT
309
+
310
+ Primary acces
311
+
312
+ ```sql
313
+ select
314
+ <concept>,
315
+ <concept>+1 -> <alias>
316
+ WHERE
317
+ <concept> = <value>
318
+ ORDER BY
319
+ <concept> asc|desc
320
+ ;
321
+ ```
322
+
323
+ #### CTE/ROWSET
324
+
325
+ Reusable virtual set of rows. Useful for windows, filtering.
326
+
327
+ ```sql
328
+ with <alias> as
329
+ select
330
+ <concept>,
331
+ <concept>+1 -> <alias>
332
+ WHERE
333
+ <concept> = <value>
334
+
335
+ select <alias>.<concept>;
336
+
337
+ ```
338
+
339
+
340
+ #### PERSIST
341
+
342
+ Store output of a query in a warehouse table
343
+
344
+ ```sql
345
+ persist <alias> as <table_name> from
346
+ <select>;
347
+ ```
348
+
349
+ #### SHOW
350
+
351
+ Return generated SQL without executing.
352
+
353
+ ```sql
354
+ show <select>;
355
+ ```
@@ -246,3 +246,81 @@ but all are worth checking out. Please open PRs/comment for anything missed!
246
246
  - [malloy](https://github.com/malloydata/malloy)
247
247
  - [preql](https://github.com/erezsh/Preql)
248
248
  - [PREQL](https://github.com/PRQL/prql)
249
+
250
+ ## Minimal Syntax Reference
251
+
252
+ #### IMPORT
253
+
254
+ `import <path> as <alias>;`
255
+
256
+ #### CONCEPT
257
+
258
+ Types: `string | int | float | bool | date | datetime | time | timestamp | interval`;
259
+
260
+ Key:
261
+ `key <name> <type>;`
262
+
263
+ Property:
264
+ `property <key>.<name> <type>;`
265
+
266
+ Transformation:
267
+ `auto <name> <- <expression>;`
268
+
269
+ #### DATASOURCE
270
+ ```sql
271
+ datasource <name>(
272
+ <column>:<concept>,
273
+ <column>:<concept>,
274
+ )
275
+ grain(<concept>, <concept>)
276
+ address <table>;
277
+ ```
278
+
279
+ #### SELECT
280
+
281
+ Primary acces
282
+
283
+ ```sql
284
+ select
285
+ <concept>,
286
+ <concept>+1 -> <alias>
287
+ WHERE
288
+ <concept> = <value>
289
+ ORDER BY
290
+ <concept> asc|desc
291
+ ;
292
+ ```
293
+
294
+ #### CTE/ROWSET
295
+
296
+ Reusable virtual set of rows. Useful for windows, filtering.
297
+
298
+ ```sql
299
+ with <alias> as
300
+ select
301
+ <concept>,
302
+ <concept>+1 -> <alias>
303
+ WHERE
304
+ <concept> = <value>
305
+
306
+ select <alias>.<concept>;
307
+
308
+ ```
309
+
310
+
311
+ #### PERSIST
312
+
313
+ Store output of a query in a warehouse table
314
+
315
+ ```sql
316
+ persist <alias> as <table_name> from
317
+ <select>;
318
+ ```
319
+
320
+ #### SHOW
321
+
322
+ Return generated SQL without executing.
323
+
324
+ ```sql
325
+ show <select>;
326
+ ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.1.105
3
+ Version: 0.0.1.107
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -275,3 +275,81 @@ but all are worth checking out. Please open PRs/comment for anything missed!
275
275
  - [malloy](https://github.com/malloydata/malloy)
276
276
  - [preql](https://github.com/erezsh/Preql)
277
277
  - [PREQL](https://github.com/PRQL/prql)
278
+
279
+ ## Minimal Syntax Reference
280
+
281
+ #### IMPORT
282
+
283
+ `import <path> as <alias>;`
284
+
285
+ #### CONCEPT
286
+
287
+ Types: `string | int | float | bool | date | datetime | time | timestamp | interval`;
288
+
289
+ Key:
290
+ `key <name> <type>;`
291
+
292
+ Property:
293
+ `property <key>.<name> <type>;`
294
+
295
+ Transformation:
296
+ `auto <name> <- <expression>;`
297
+
298
+ #### DATASOURCE
299
+ ```sql
300
+ datasource <name>(
301
+ <column>:<concept>,
302
+ <column>:<concept>,
303
+ )
304
+ grain(<concept>, <concept>)
305
+ address <table>;
306
+ ```
307
+
308
+ #### SELECT
309
+
310
+ Primary acces
311
+
312
+ ```sql
313
+ select
314
+ <concept>,
315
+ <concept>+1 -> <alias>
316
+ WHERE
317
+ <concept> = <value>
318
+ ORDER BY
319
+ <concept> asc|desc
320
+ ;
321
+ ```
322
+
323
+ #### CTE/ROWSET
324
+
325
+ Reusable virtual set of rows. Useful for windows, filtering.
326
+
327
+ ```sql
328
+ with <alias> as
329
+ select
330
+ <concept>,
331
+ <concept>+1 -> <alias>
332
+ WHERE
333
+ <concept> = <value>
334
+
335
+ select <alias>.<concept>;
336
+
337
+ ```
338
+
339
+
340
+ #### PERSIST
341
+
342
+ Store output of a query in a warehouse table
343
+
344
+ ```sql
345
+ persist <alias> as <table_name> from
346
+ <select>;
347
+ ```
348
+
349
+ #### SHOW
350
+
351
+ Return generated SQL without executing.
352
+
353
+ ```sql
354
+ show <select>;
355
+ ```
@@ -43,6 +43,7 @@ trilogy/core/functions.py
43
43
  trilogy/core/graph_models.py
44
44
  trilogy/core/internal.py
45
45
  trilogy/core/models.py
46
+ trilogy/core/optimization.py
46
47
  trilogy/core/query_processor.py
47
48
  trilogy/core/processing/__init__.py
48
49
  trilogy/core/processing/concept_strategies_v3.py
@@ -125,4 +125,5 @@ def test_modifiers():
125
125
  generator = BigqueryDialect()
126
126
 
127
127
  text = generator.compile_statement(query)
128
- assert "`b` = 2" in text
128
+ assert "2 = 2" in text
129
+ assert "as `b`" not in text
@@ -2,7 +2,8 @@ from trilogy.core.models import Environment
2
2
  from trilogy.dialect.enums import Dialects
3
3
  from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
+ from trilogy.constants import CONFIG
5
6
 
6
- __version__ = "0.0.1.105"
7
+ __version__ = "0.0.1.107"
7
8
 
8
- __all__ = ["parse", "Executor", "Dialects", "Environment"]
9
+ __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -23,6 +23,7 @@ NULL_VALUE = MagicConstants.NULL
23
23
  class Config:
24
24
  strict_mode: bool = True
25
25
  human_identifiers: bool = True
26
+ inline_datasources: bool = True
26
27
 
27
28
 
28
29
  CONFIG = Config()
@@ -34,7 +34,11 @@ from pydantic import (
34
34
  from lark.tree import Meta
35
35
  from pathlib import Path
36
36
  from trilogy.constants import logger, DEFAULT_NAMESPACE, ENV_CACHE_NAME, MagicConstants
37
- from trilogy.core.constants import ALL_ROWS_CONCEPT, INTERNAL_NAMESPACE
37
+ from trilogy.core.constants import (
38
+ ALL_ROWS_CONCEPT,
39
+ INTERNAL_NAMESPACE,
40
+ CONSTANT_DATASET,
41
+ )
38
42
  from trilogy.core.enums import (
39
43
  InfiniteFunctionArgs,
40
44
  Purpose,
@@ -1434,6 +1438,7 @@ class MultiSelectStatement(Namespaced, BaseModel):
1434
1438
 
1435
1439
  class Address(BaseModel):
1436
1440
  location: str
1441
+ is_query: bool = False
1437
1442
 
1438
1443
 
1439
1444
  class Query(BaseModel):
@@ -1529,10 +1534,27 @@ class Datasource(Namespaced, BaseModel):
1529
1534
  default_factory=lambda: DatasourceMetadata(freshness_concept=None)
1530
1535
  )
1531
1536
 
1537
+ @property
1538
+ def condition(self):
1539
+ return None
1540
+
1532
1541
  @cached_property
1533
1542
  def output_lcl(self) -> LooseConceptList:
1534
1543
  return LooseConceptList(concepts=self.output_concepts)
1535
1544
 
1545
+ @property
1546
+ def can_be_inlined(self) -> bool:
1547
+ if isinstance(self.address, Address) and self.address.is_query:
1548
+ return False
1549
+ for x in self.columns:
1550
+ if not isinstance(x.alias, str):
1551
+ return False
1552
+ return True
1553
+
1554
+ @property
1555
+ def non_partial_concept_addresses(self) -> set[str]:
1556
+ return set([c.address for c in self.full_concepts])
1557
+
1536
1558
  @field_validator("namespace", mode="plain")
1537
1559
  @classmethod
1538
1560
  def namespace_validation(cls, v):
@@ -1647,7 +1669,7 @@ class Datasource(Namespaced, BaseModel):
1647
1669
  namespace = self.namespace.replace(".", "_") if self.namespace else ""
1648
1670
  return f"{namespace}_{self.identifier}"
1649
1671
 
1650
- @cached_property
1672
+ @property
1651
1673
  def safe_location(self) -> str:
1652
1674
  if isinstance(self.address, Address):
1653
1675
  return self.address.location
@@ -1746,7 +1768,7 @@ class QueryDatasource(BaseModel):
1746
1768
  input_concepts: List[Concept]
1747
1769
  output_concepts: List[Concept]
1748
1770
  source_map: Dict[str, Set[Union[Datasource, "QueryDatasource", "UnnestJoin"]]]
1749
- datasources: Sequence[Union[Datasource, "QueryDatasource"]]
1771
+ datasources: List[Union[Datasource, "QueryDatasource"]]
1750
1772
  grain: Grain
1751
1773
  joins: List[BaseJoin | UnnestJoin]
1752
1774
  limit: Optional[int] = None
@@ -1797,10 +1819,7 @@ class QueryDatasource(BaseModel):
1797
1819
  c.address for c in values["input_concepts"]
1798
1820
  )
1799
1821
  seen = set()
1800
- for k, val in v.items():
1801
- # if val:
1802
- # if len(val) != 1:
1803
- # raise SyntaxError(f"source map {k} has multiple values {len(val)}")
1822
+ for k, _ in v.items():
1804
1823
  seen.add(k)
1805
1824
  for x in expected:
1806
1825
  if x not in seen:
@@ -1922,18 +1941,18 @@ class QueryDatasource(BaseModel):
1922
1941
  )
1923
1942
 
1924
1943
  def get_alias(
1925
- self, concept: Concept, use_raw_name: bool = False, force_alias: bool = False
1944
+ self,
1945
+ concept: Concept,
1946
+ use_raw_name: bool = False,
1947
+ force_alias: bool = False,
1948
+ source: str | None = None,
1926
1949
  ):
1927
- # if we should use the raw datasource name to access
1928
- use_raw_name = (
1929
- True
1930
- if (len(self.datasources) == 1 or use_raw_name) and not force_alias
1931
- # if ((len(self.datasources) == 1 and isinstance(self.datasources[0], Datasource)) or use_raw_name) and not force_alias
1932
- else False
1933
- )
1934
1950
  for x in self.datasources:
1935
1951
  # query datasources should be referenced by their alias, always
1936
1952
  force_alias = isinstance(x, QueryDatasource)
1953
+ use_raw_name = isinstance(x, Datasource) and not force_alias
1954
+ if source and x.identifier != source:
1955
+ continue
1937
1956
  try:
1938
1957
  return x.get_alias(
1939
1958
  concept.with_grain(self.grain),
@@ -1967,8 +1986,7 @@ class Comment(BaseModel):
1967
1986
 
1968
1987
  class CTE(BaseModel):
1969
1988
  name: str
1970
- source: "QueryDatasource" # TODO: make recursive
1971
- # output columns are what are selected/grouped by
1989
+ source: "QueryDatasource"
1972
1990
  output_columns: List[Concept]
1973
1991
  source_map: Dict[str, str | list[str]]
1974
1992
  grain: Grain
@@ -1979,6 +1997,10 @@ class CTE(BaseModel):
1979
1997
  condition: Optional[Union["Conditional", "Comparison", "Parenthetical"]] = None
1980
1998
  partial_concepts: List[Concept] = Field(default_factory=list)
1981
1999
  join_derived_concepts: List[Concept] = Field(default_factory=list)
2000
+ order_by: Optional[OrderBy] = None
2001
+ limit: Optional[int] = None
2002
+ requires_nesting: bool = True
2003
+ base_name_override: Optional[str] = None
1982
2004
 
1983
2005
  @computed_field # type: ignore
1984
2006
  @property
@@ -1989,6 +2011,40 @@ class CTE(BaseModel):
1989
2011
  def validate_output_columns(cls, v):
1990
2012
  return unique(v, "address")
1991
2013
 
2014
+ def inline_parent_datasource(self, parent: CTE) -> bool:
2015
+ qds_being_inlined = parent.source
2016
+ ds_being_inlined = qds_being_inlined.datasources[0]
2017
+ if not isinstance(ds_being_inlined, Datasource):
2018
+ return False
2019
+ self.source.datasources = [
2020
+ ds_being_inlined,
2021
+ *[
2022
+ x
2023
+ for x in self.source.datasources
2024
+ if x.identifier != qds_being_inlined.identifier
2025
+ ],
2026
+ ]
2027
+ # need to identify this before updating joins
2028
+ if self.base_name == parent.name:
2029
+ self.base_name_override = ds_being_inlined.safe_location
2030
+
2031
+ for join in self.joins:
2032
+ if isinstance(join, InstantiatedUnnestJoin):
2033
+ continue
2034
+ if join.left_cte.name == parent.name:
2035
+ join.left_cte = ds_being_inlined
2036
+ if join.right_cte.name == parent.name:
2037
+ join.right_cte = ds_being_inlined
2038
+ for k, v in self.source_map.items():
2039
+ if isinstance(v, list):
2040
+ self.source_map[k] = [
2041
+ ds_being_inlined.name if x == parent.name else x for x in v
2042
+ ]
2043
+ elif v == parent.name:
2044
+ self.source_map[k] = ds_being_inlined.name
2045
+ self.parent_ctes = [x for x in self.parent_ctes if x.name != parent.name]
2046
+ return True
2047
+
1992
2048
  def __add__(self, other: "CTE"):
1993
2049
  logger.info('Merging two copies of CTE "%s"', self.name)
1994
2050
  if not self.grain == other.grain:
@@ -2031,16 +2087,25 @@ class CTE(BaseModel):
2031
2087
  def relevant_base_ctes(self):
2032
2088
  return self.parent_ctes
2033
2089
 
2090
+ @property
2091
+ def is_root_datasource(self) -> bool:
2092
+ return (
2093
+ len(self.source.datasources) == 1
2094
+ and isinstance(self.source.datasources[0], Datasource)
2095
+ and not self.source.datasources[0].name == CONSTANT_DATASET
2096
+ )
2097
+
2034
2098
  @property
2035
2099
  def base_name(self) -> str:
2100
+ if self.base_name_override:
2101
+ return self.base_name_override
2036
2102
  # if this cte selects from a single datasource, select right from it
2037
2103
  valid_joins: List[Join] = [
2038
2104
  join for join in self.joins if isinstance(join, Join)
2039
2105
  ]
2040
- if len(self.source.datasources) == 1 and isinstance(
2041
- self.source.datasources[0], Datasource
2042
- ):
2106
+ if self.is_root_datasource:
2043
2107
  return self.source.datasources[0].safe_location
2108
+
2044
2109
  # if we have multiple joined CTEs, pick the base
2045
2110
  # as the root
2046
2111
  elif len(self.source.datasources) == 1 and len(self.parent_ctes) == 1:
@@ -2066,11 +2131,10 @@ class CTE(BaseModel):
2066
2131
 
2067
2132
  @property
2068
2133
  def base_alias(self) -> str:
2134
+
2135
+ if self.is_root_datasource:
2136
+ return self.source.datasources[0].identifier
2069
2137
  relevant_joins = [j for j in self.joins if isinstance(j, Join)]
2070
- if len(self.source.datasources) == 1 and isinstance(
2071
- self.source.datasources[0], Datasource
2072
- ):
2073
- return self.source.datasources[0].full_name.replace(".", "_")
2074
2138
  if relevant_joins:
2075
2139
  return relevant_joins[0].left_cte.name
2076
2140
  elif self.relevant_base_ctes:
@@ -2079,12 +2143,16 @@ class CTE(BaseModel):
2079
2143
  return self.parent_ctes[0].name
2080
2144
  return self.name
2081
2145
 
2082
- def get_alias(self, concept: Concept) -> str:
2146
+ def get_alias(self, concept: Concept, source: str | None = None) -> str:
2083
2147
  for cte in self.parent_ctes:
2084
2148
  if concept.address in [x.address for x in cte.output_columns]:
2149
+ if source and source != cte.name:
2150
+ continue
2085
2151
  return concept.safe_address
2086
2152
  try:
2087
- source = self.source.get_alias(concept)
2153
+ source = self.source.get_alias(concept, source=source)
2154
+ if not source:
2155
+ raise ValueError("No source found")
2088
2156
  return source
2089
2157
  except ValueError as e:
2090
2158
  return f"INVALID_ALIAS: {str(e)}"
@@ -2097,6 +2165,11 @@ class CTE(BaseModel):
2097
2165
  and not self.group_to_grain
2098
2166
  ):
2099
2167
  return False
2168
+ if (
2169
+ len(self.source.datasources) == 1
2170
+ and self.source.datasources[0].name == CONSTANT_DATASET
2171
+ ):
2172
+ return False
2100
2173
  return True
2101
2174
 
2102
2175
  @property
@@ -2130,19 +2203,43 @@ class JoinKey(BaseModel):
2130
2203
 
2131
2204
 
2132
2205
  class Join(BaseModel):
2133
- left_cte: CTE
2134
- right_cte: CTE
2206
+ left_cte: CTE | Datasource
2207
+ right_cte: CTE | Datasource
2135
2208
  jointype: JoinType
2136
2209
  joinkeys: List[JoinKey]
2137
2210
 
2211
+ @property
2212
+ def left_name(self) -> str:
2213
+ if isinstance(self.left_cte, Datasource):
2214
+ return self.left_cte.identifier
2215
+ return self.left_cte.name
2216
+
2217
+ @property
2218
+ def right_name(self) -> str:
2219
+ if isinstance(self.right_cte, Datasource):
2220
+ return self.right_cte.identifier
2221
+ return self.right_cte.name
2222
+
2223
+ @property
2224
+ def left_ref(self) -> str:
2225
+ if isinstance(self.left_cte, Datasource):
2226
+ return f"{self.left_cte.safe_location} as {self.left_cte.identifier}"
2227
+ return self.left_cte.name
2228
+
2229
+ @property
2230
+ def right_ref(self) -> str:
2231
+ if isinstance(self.right_cte, Datasource):
2232
+ return f"{self.right_cte.safe_location} as {self.right_cte.identifier}"
2233
+ return self.right_cte.name
2234
+
2138
2235
  @property
2139
2236
  def unique_id(self) -> str:
2140
- return self.left_cte.name + self.right_cte.name + self.jointype.value
2237
+ return self.left_name + self.right_name + self.jointype.value
2141
2238
 
2142
2239
  def __str__(self):
2143
2240
  return (
2144
- f"{self.jointype.value} JOIN {self.left_cte.name} and"
2145
- f" {self.right_cte.name} on {','.join([str(k) for k in self.joinkeys])}"
2241
+ f"{self.jointype.value} JOIN {self.left_name} and"
2242
+ f" {self.right_name} on {','.join([str(k) for k in self.joinkeys])}"
2146
2243
  )
2147
2244
 
2148
2245