pytrilogy 0.0.2.46__py3-none-any.whl → 0.0.2.48__py3-none-any.whl

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 (69) hide show
  1. {pytrilogy-0.0.2.46.dist-info → pytrilogy-0.0.2.48.dist-info}/METADATA +1 -1
  2. pytrilogy-0.0.2.48.dist-info/RECORD +85 -0
  3. trilogy/__init__.py +2 -2
  4. trilogy/constants.py +4 -2
  5. trilogy/core/enums.py +7 -1
  6. trilogy/core/env_processor.py +1 -2
  7. trilogy/core/environment_helpers.py +5 -5
  8. trilogy/core/functions.py +11 -10
  9. trilogy/core/internal.py +2 -3
  10. trilogy/core/models.py +448 -394
  11. trilogy/core/optimization.py +37 -21
  12. trilogy/core/optimizations/__init__.py +1 -1
  13. trilogy/core/optimizations/base_optimization.py +6 -6
  14. trilogy/core/optimizations/inline_constant.py +7 -4
  15. trilogy/core/optimizations/inline_datasource.py +14 -5
  16. trilogy/core/optimizations/predicate_pushdown.py +20 -10
  17. trilogy/core/processing/concept_strategies_v3.py +40 -24
  18. trilogy/core/processing/graph_utils.py +2 -3
  19. trilogy/core/processing/node_generators/__init__.py +7 -5
  20. trilogy/core/processing/node_generators/basic_node.py +4 -4
  21. trilogy/core/processing/node_generators/common.py +10 -11
  22. trilogy/core/processing/node_generators/filter_node.py +7 -9
  23. trilogy/core/processing/node_generators/group_node.py +10 -11
  24. trilogy/core/processing/node_generators/group_to_node.py +5 -5
  25. trilogy/core/processing/node_generators/multiselect_node.py +10 -12
  26. trilogy/core/processing/node_generators/node_merge_node.py +7 -9
  27. trilogy/core/processing/node_generators/rowset_node.py +9 -8
  28. trilogy/core/processing/node_generators/select_merge_node.py +11 -10
  29. trilogy/core/processing/node_generators/select_node.py +5 -5
  30. trilogy/core/processing/node_generators/union_node.py +75 -0
  31. trilogy/core/processing/node_generators/unnest_node.py +2 -3
  32. trilogy/core/processing/node_generators/window_node.py +3 -4
  33. trilogy/core/processing/nodes/__init__.py +9 -5
  34. trilogy/core/processing/nodes/base_node.py +17 -13
  35. trilogy/core/processing/nodes/filter_node.py +3 -4
  36. trilogy/core/processing/nodes/group_node.py +8 -10
  37. trilogy/core/processing/nodes/merge_node.py +11 -11
  38. trilogy/core/processing/nodes/select_node_v2.py +8 -9
  39. trilogy/core/processing/nodes/union_node.py +50 -0
  40. trilogy/core/processing/nodes/unnest_node.py +2 -3
  41. trilogy/core/processing/nodes/window_node.py +2 -3
  42. trilogy/core/processing/utility.py +37 -40
  43. trilogy/core/query_processor.py +68 -44
  44. trilogy/dialect/base.py +95 -53
  45. trilogy/dialect/bigquery.py +2 -3
  46. trilogy/dialect/common.py +5 -4
  47. trilogy/dialect/config.py +0 -2
  48. trilogy/dialect/duckdb.py +2 -2
  49. trilogy/dialect/enums.py +5 -5
  50. trilogy/dialect/postgres.py +2 -2
  51. trilogy/dialect/presto.py +3 -4
  52. trilogy/dialect/snowflake.py +2 -2
  53. trilogy/dialect/sql_server.py +3 -4
  54. trilogy/engine.py +2 -1
  55. trilogy/executor.py +43 -30
  56. trilogy/hooks/base_hook.py +5 -4
  57. trilogy/hooks/graph_hook.py +2 -1
  58. trilogy/hooks/query_debugger.py +18 -8
  59. trilogy/parsing/common.py +15 -20
  60. trilogy/parsing/parse_engine.py +124 -88
  61. trilogy/parsing/render.py +32 -35
  62. trilogy/parsing/trilogy.lark +8 -1
  63. trilogy/scripts/trilogy.py +6 -4
  64. trilogy/utility.py +1 -1
  65. pytrilogy-0.0.2.46.dist-info/RECORD +0 -83
  66. {pytrilogy-0.0.2.46.dist-info → pytrilogy-0.0.2.48.dist-info}/LICENSE.md +0 -0
  67. {pytrilogy-0.0.2.46.dist-info → pytrilogy-0.0.2.48.dist-info}/WHEEL +0 -0
  68. {pytrilogy-0.0.2.46.dist-info → pytrilogy-0.0.2.48.dist-info}/entry_points.txt +0 -0
  69. {pytrilogy-0.0.2.46.dist-info → pytrilogy-0.0.2.48.dist-info}/top_level.txt +0 -0
trilogy/executor.py CHANGED
@@ -1,39 +1,40 @@
1
- from typing import List, Optional, Any, Generator, Protocol
1
+ from dataclasses import dataclass
2
2
  from functools import singledispatchmethod
3
+ from pathlib import Path
4
+ from typing import Any, Generator, List, Optional, Protocol
5
+
3
6
  from sqlalchemy import text
4
- from sqlalchemy.engine import Engine, CursorResult
7
+ from sqlalchemy.engine import CursorResult, Engine
5
8
 
6
9
  from trilogy.constants import logger
10
+ from trilogy.core.enums import Granularity, IOType
7
11
  from trilogy.core.models import (
12
+ Concept,
13
+ ConceptDeclarationStatement,
14
+ CopyStatement,
15
+ Datasource,
8
16
  Environment,
17
+ Function,
18
+ FunctionType,
19
+ ImportStatement,
20
+ ListWrapper,
21
+ MapWrapper,
22
+ MergeStatementV2,
23
+ MultiSelectStatement,
24
+ PersistStatement,
25
+ ProcessedCopyStatement,
9
26
  ProcessedQuery,
10
- ProcessedShowStatement,
11
27
  ProcessedQueryPersist,
12
28
  ProcessedRawSQLStatement,
13
- ProcessedCopyStatement,
29
+ ProcessedShowStatement,
14
30
  RawSQLStatement,
15
- MultiSelectStatement,
16
31
  SelectStatement,
17
- PersistStatement,
18
32
  ShowStatement,
19
- Concept,
20
- ConceptDeclarationStatement,
21
- Datasource,
22
- CopyStatement,
23
- ImportStatement,
24
- MergeStatementV2,
25
- Function,
26
- FunctionType,
27
- MapWrapper,
28
- ListWrapper,
29
33
  )
30
34
  from trilogy.dialect.base import BaseDialect
31
35
  from trilogy.dialect.enums import Dialects
32
- from trilogy.core.enums import IOType, Granularity
33
- from trilogy.parser import parse_text
34
36
  from trilogy.hooks.base_hook import BaseHook
35
- from pathlib import Path
36
- from dataclasses import dataclass
37
+ from trilogy.parser import parse_text
37
38
 
38
39
 
39
40
  class ResultProtocol(Protocol):
@@ -103,7 +104,6 @@ class Executor(object):
103
104
 
104
105
  self.generator = PostgresDialect()
105
106
  elif self.dialect == Dialects.SNOWFLAKE:
106
-
107
107
  from trilogy.dialect.snowflake import SnowflakeDialect
108
108
 
109
109
  self.generator = SnowflakeDialect()
@@ -146,7 +146,6 @@ class Executor(object):
146
146
 
147
147
  @execute_query.register
148
148
  def _(self, query: Datasource) -> CursorResult:
149
-
150
149
  return MockResult(
151
150
  [
152
151
  {
@@ -232,22 +231,23 @@ class Executor(object):
232
231
  @execute_query.register
233
232
  def _(self, query: ProcessedQuery) -> CursorResult:
234
233
  sql = self.generator.compile_statement(query)
235
- output = self.execute_raw_sql(sql)
234
+ output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
236
235
  return output
237
236
 
238
237
  @execute_query.register
239
238
  def _(self, query: ProcessedQueryPersist) -> CursorResult:
240
-
241
239
  sql = self.generator.compile_statement(query)
242
240
 
243
- output = self.execute_raw_sql(sql)
241
+ output = self.execute_raw_sql(sql, local_concepts=query.local_concepts)
244
242
  self.environment.add_datasource(query.datasource)
245
243
  return output
246
244
 
247
245
  @execute_query.register
248
246
  def _(self, query: ProcessedCopyStatement) -> CursorResult:
249
247
  sql = self.generator.compile_statement(query)
250
- output: CursorResult = self.execute_raw_sql(sql)
248
+ output: CursorResult = self.execute_raw_sql(
249
+ sql, local_concepts=query.local_concepts
250
+ )
251
251
  if query.target_type == IOType.CSV:
252
252
  import csv
253
253
 
@@ -355,7 +355,6 @@ class Executor(object):
355
355
  | ProcessedRawSQLStatement
356
356
  | ProcessedCopyStatement
357
357
  ]:
358
-
359
358
  return list(self.parse_text_generator(command, persist=persist))
360
359
 
361
360
  def parse_text_generator(self, command: str, persist: bool = False) -> Generator[
@@ -395,12 +394,20 @@ class Executor(object):
395
394
  if persist and isinstance(x, ProcessedQueryPersist):
396
395
  self.environment.add_datasource(x.datasource)
397
396
 
398
- def _hydrate_param(self, param: str) -> Any:
397
+ def _hydrate_param(
398
+ self, param: str, local_concepts: dict[str, Concept] | None = None
399
+ ) -> Any:
399
400
  matched = [
400
401
  v
401
402
  for v in self.environment.concepts.values()
402
403
  if v.safe_address == param or v.address == param
403
404
  ]
405
+ if local_concepts and not matched:
406
+ matched = [
407
+ v
408
+ for v in local_concepts.values()
409
+ if v.safe_address == param or v.address == param
410
+ ]
404
411
  if not matched:
405
412
  raise SyntaxError(f"No concept found for parameter {param}")
406
413
 
@@ -424,7 +431,10 @@ class Executor(object):
424
431
  return results[0]
425
432
 
426
433
  def execute_raw_sql(
427
- self, command: str, variables: dict | None = None
434
+ self,
435
+ command: str,
436
+ variables: dict | None = None,
437
+ local_concepts: dict[str, Concept] | None = None,
428
438
  ) -> CursorResult:
429
439
  """Run a command against the raw underlying
430
440
  execution engine"""
@@ -435,7 +445,10 @@ class Executor(object):
435
445
  else:
436
446
  params = q.compile().params
437
447
  if params:
438
- final_params = {x: self._hydrate_param(x) for x in params}
448
+ final_params = {
449
+ x: self._hydrate_param(x, local_concepts=local_concepts)
450
+ for x in params
451
+ }
439
452
 
440
453
  if final_params:
441
454
  return self.connection.execute(text(command), final_params)
@@ -1,10 +1,11 @@
1
1
  from trilogy.core.models import (
2
- QueryDatasource,
3
2
  CTE,
4
- SelectStatement,
5
- PersistStatement,
6
3
  MultiSelectStatement,
4
+ PersistStatement,
5
+ QueryDatasource,
7
6
  RowsetDerivationStatement,
7
+ SelectStatement,
8
+ UnionCTE,
8
9
  )
9
10
  from trilogy.core.processing.nodes import StrategyNode
10
11
 
@@ -30,7 +31,7 @@ class BaseHook:
30
31
  def process_root_datasource(self, datasource: QueryDatasource):
31
32
  pass
32
33
 
33
- def process_root_cte(self, cte: CTE):
34
+ def process_root_cte(self, cte: CTE | UnionCTE):
34
35
  pass
35
36
 
36
37
  def process_root_strategy_node(self, node: StrategyNode):
@@ -1,6 +1,7 @@
1
- from trilogy.hooks.base_hook import BaseHook
2
1
  import networkx as nx
3
2
 
3
+ from trilogy.hooks.base_hook import BaseHook
4
+
4
5
 
5
6
  class GraphHook(BaseHook):
6
7
  def __init__(self):
@@ -1,14 +1,18 @@
1
+ from enum import Enum
2
+ from logging import DEBUG, StreamHandler
1
3
  from typing import Union
2
- from trilogy.core.models import QueryDatasource, CTE, Datasource, SelectStatement
3
4
 
4
- from trilogy.hooks.base_hook import BaseHook
5
5
  from trilogy.constants import logger
6
- from logging import StreamHandler, DEBUG
6
+ from trilogy.core.models import (
7
+ CTE,
8
+ Datasource,
9
+ QueryDatasource,
10
+ SelectStatement,
11
+ UnionCTE,
12
+ )
7
13
  from trilogy.core.processing.nodes import StrategyNode
8
-
9
14
  from trilogy.dialect.bigquery import BigqueryDialect
10
-
11
- from enum import Enum
15
+ from trilogy.hooks.base_hook import BaseHook
12
16
 
13
17
 
14
18
  class PrintMode(Enum):
@@ -85,7 +89,9 @@ def print_recursive_nodes(
85
89
  return display
86
90
 
87
91
 
88
- def print_recursive_ctes(input: CTE, depth: int = 0, max_depth: int | None = None):
92
+ def print_recursive_ctes(
93
+ input: CTE | UnionCTE, depth: int = 0, max_depth: int | None = None
94
+ ):
89
95
  if max_depth and depth > max_depth:
90
96
  return
91
97
  select_statement = [c.address for c in input.output_columns]
@@ -96,6 +102,10 @@ def print_recursive_ctes(input: CTE, depth: int = 0, max_depth: int | None = Non
96
102
  if isinstance(input, CTE):
97
103
  for child in input.parent_ctes:
98
104
  print_recursive_ctes(child, depth + 1)
105
+ elif isinstance(input, UnionCTE):
106
+ for child in input.parent_ctes:
107
+ for parent in child.parent_ctes:
108
+ print_recursive_ctes(parent, depth + 1)
99
109
 
100
110
 
101
111
  class DebuggingHook(BaseHook):
@@ -128,7 +138,7 @@ class DebuggingHook(BaseHook):
128
138
  for row in printed:
129
139
  print("".join([str(v) for v in row]))
130
140
 
131
- def process_root_cte(self, cte: CTE):
141
+ def process_root_cte(self, cte: CTE | UnionCTE):
132
142
  if self.process_ctes != PrintMode.OFF:
133
143
  print_recursive_ctes(cte, max_depth=self.max_depth)
134
144
 
trilogy/parsing/common.py CHANGED
@@ -1,32 +1,28 @@
1
+ from typing import List, Tuple
2
+
3
+ from trilogy.constants import (
4
+ VIRTUAL_CONCEPT_PREFIX,
5
+ )
6
+ from trilogy.core.enums import FunctionType, Modifier, PurposeLineage, WindowType
7
+ from trilogy.core.functions import arg_to_datatype, function_args_to_output_purpose
1
8
  from trilogy.core.models import (
2
9
  AggregateWrapper,
3
10
  Concept,
11
+ DataType,
12
+ Environment,
13
+ FilterItem,
4
14
  Function,
15
+ FunctionClass,
5
16
  Grain,
6
- Purpose,
7
- Metadata,
8
- FilterItem,
9
17
  ListWrapper,
10
18
  MapWrapper,
11
- WindowItem,
12
19
  Meta,
20
+ Metadata,
13
21
  Parenthetical,
14
- FunctionClass,
15
- Environment,
16
- DataType,
17
- )
18
- from typing import List, Tuple
19
- from trilogy.core.functions import (
20
- function_args_to_output_purpose,
21
- FunctionType,
22
- arg_to_datatype,
23
- )
24
- from trilogy.utility import unique, string_to_hash
25
- from trilogy.core.enums import PurposeLineage
26
- from trilogy.constants import (
27
- VIRTUAL_CONCEPT_PREFIX,
22
+ Purpose,
23
+ WindowItem,
28
24
  )
29
- from trilogy.core.enums import Modifier, WindowType
25
+ from trilogy.utility import string_to_hash, unique
30
26
 
31
27
 
32
28
  def get_upstream_modifiers(keys: List[Concept]) -> list[Modifier]:
@@ -304,7 +300,6 @@ def arbitrary_to_concept(
304
300
  metadata: Metadata | None = None,
305
301
  purpose: Purpose | None = None,
306
302
  ) -> Concept:
307
-
308
303
  if isinstance(parent, AggregateWrapper):
309
304
  if not name:
310
305
  name = f"{VIRTUAL_CONCEPT_PREFIX}_agg_{parent.function.operator.value}_{string_to_hash(str(parent))}"