pytrilogy 0.0.2.57__py3-none-any.whl → 0.0.3.0__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.
Files changed (75) hide show
  1. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/METADATA +9 -2
  2. pytrilogy-0.0.3.0.dist-info/RECORD +99 -0
  3. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/WHEEL +1 -1
  4. trilogy/__init__.py +2 -2
  5. trilogy/core/enums.py +1 -7
  6. trilogy/core/env_processor.py +17 -5
  7. trilogy/core/environment_helpers.py +11 -25
  8. trilogy/core/exceptions.py +4 -0
  9. trilogy/core/functions.py +695 -261
  10. trilogy/core/graph_models.py +10 -10
  11. trilogy/core/internal.py +11 -2
  12. trilogy/core/models/__init__.py +0 -0
  13. trilogy/core/models/author.py +2110 -0
  14. trilogy/core/models/build.py +1845 -0
  15. trilogy/core/models/build_environment.py +151 -0
  16. trilogy/core/models/core.py +370 -0
  17. trilogy/core/models/datasource.py +297 -0
  18. trilogy/core/models/environment.py +696 -0
  19. trilogy/core/models/execute.py +931 -0
  20. trilogy/core/optimization.py +17 -22
  21. trilogy/core/optimizations/base_optimization.py +1 -1
  22. trilogy/core/optimizations/inline_constant.py +6 -6
  23. trilogy/core/optimizations/inline_datasource.py +17 -11
  24. trilogy/core/optimizations/predicate_pushdown.py +17 -16
  25. trilogy/core/processing/concept_strategies_v3.py +181 -146
  26. trilogy/core/processing/graph_utils.py +1 -1
  27. trilogy/core/processing/node_generators/basic_node.py +19 -18
  28. trilogy/core/processing/node_generators/common.py +51 -45
  29. trilogy/core/processing/node_generators/filter_node.py +26 -13
  30. trilogy/core/processing/node_generators/group_node.py +26 -21
  31. trilogy/core/processing/node_generators/group_to_node.py +13 -10
  32. trilogy/core/processing/node_generators/multiselect_node.py +60 -43
  33. trilogy/core/processing/node_generators/node_merge_node.py +76 -38
  34. trilogy/core/processing/node_generators/rowset_node.py +59 -36
  35. trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +27 -34
  36. trilogy/core/processing/node_generators/select_merge_node.py +161 -64
  37. trilogy/core/processing/node_generators/select_node.py +13 -13
  38. trilogy/core/processing/node_generators/union_node.py +12 -11
  39. trilogy/core/processing/node_generators/unnest_node.py +9 -7
  40. trilogy/core/processing/node_generators/window_node.py +19 -16
  41. trilogy/core/processing/nodes/__init__.py +21 -18
  42. trilogy/core/processing/nodes/base_node.py +92 -77
  43. trilogy/core/processing/nodes/filter_node.py +19 -13
  44. trilogy/core/processing/nodes/group_node.py +55 -40
  45. trilogy/core/processing/nodes/merge_node.py +47 -38
  46. trilogy/core/processing/nodes/select_node_v2.py +54 -40
  47. trilogy/core/processing/nodes/union_node.py +5 -7
  48. trilogy/core/processing/nodes/unnest_node.py +7 -11
  49. trilogy/core/processing/nodes/window_node.py +9 -4
  50. trilogy/core/processing/utility.py +108 -80
  51. trilogy/core/query_processor.py +67 -49
  52. trilogy/core/statements/__init__.py +0 -0
  53. trilogy/core/statements/author.py +413 -0
  54. trilogy/core/statements/build.py +0 -0
  55. trilogy/core/statements/common.py +30 -0
  56. trilogy/core/statements/execute.py +42 -0
  57. trilogy/dialect/base.py +152 -111
  58. trilogy/dialect/common.py +9 -10
  59. trilogy/dialect/duckdb.py +1 -1
  60. trilogy/dialect/enums.py +4 -2
  61. trilogy/dialect/presto.py +1 -1
  62. trilogy/dialect/sql_server.py +1 -1
  63. trilogy/executor.py +44 -32
  64. trilogy/hooks/base_hook.py +6 -4
  65. trilogy/hooks/query_debugger.py +110 -93
  66. trilogy/parser.py +1 -1
  67. trilogy/parsing/common.py +303 -64
  68. trilogy/parsing/parse_engine.py +263 -617
  69. trilogy/parsing/render.py +50 -26
  70. trilogy/scripts/trilogy.py +2 -1
  71. pytrilogy-0.0.2.57.dist-info/RECORD +0 -87
  72. trilogy/core/models.py +0 -4960
  73. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/LICENSE.md +0 -0
  74. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/entry_points.txt +0 -0
  75. {pytrilogy-0.0.2.57.dist-info → pytrilogy-0.0.3.0.dist-info}/top_level.txt +0 -0
trilogy/executor.py CHANGED
@@ -7,29 +7,29 @@ from sqlalchemy import text
7
7
  from sqlalchemy.engine import CursorResult, Engine
8
8
 
9
9
  from trilogy.constants import logger
10
- from trilogy.core.enums import Granularity, IOType
11
- from trilogy.core.models import (
12
- Concept,
10
+ from trilogy.core.enums import FunctionType, Granularity, IOType
11
+ from trilogy.core.models.author import Concept, Function
12
+ from trilogy.core.models.build import BuildConcept, BuildFunction
13
+ from trilogy.core.models.core import ListWrapper, MapWrapper
14
+ from trilogy.core.models.datasource import Datasource
15
+ from trilogy.core.models.environment import Environment
16
+ from trilogy.core.statements.author import (
13
17
  ConceptDeclarationStatement,
14
18
  CopyStatement,
15
- Datasource,
16
- Environment,
17
- Function,
18
- FunctionType,
19
19
  ImportStatement,
20
- ListWrapper,
21
- MapWrapper,
22
20
  MergeStatementV2,
23
21
  MultiSelectStatement,
24
22
  PersistStatement,
23
+ RawSQLStatement,
24
+ SelectStatement,
25
+ ShowStatement,
26
+ )
27
+ from trilogy.core.statements.execute import (
25
28
  ProcessedCopyStatement,
26
29
  ProcessedQuery,
27
30
  ProcessedQueryPersist,
28
31
  ProcessedRawSQLStatement,
29
32
  ProcessedShowStatement,
30
- RawSQLStatement,
31
- SelectStatement,
32
- ShowStatement,
33
33
  )
34
34
  from trilogy.dialect.base import BaseDialect
35
35
  from trilogy.dialect.enums import Dialects
@@ -58,7 +58,9 @@ class MockResult:
58
58
  return self.columns
59
59
 
60
60
 
61
- def generate_result_set(columns: List[Concept], output_data: list[Any]) -> MockResult:
61
+ def generate_result_set(
62
+ columns: List[BuildConcept], output_data: list[Any]
63
+ ) -> MockResult:
62
64
  names = [x.address.replace(".", "_") for x in columns]
63
65
  return MockResult(
64
66
  values=[dict(zip(names, [row])) for row in output_data], columns=names
@@ -394,6 +396,32 @@ class Executor(object):
394
396
  if persist and isinstance(x, ProcessedQueryPersist):
395
397
  self.environment.add_datasource(x.datasource)
396
398
 
399
+ def _concept_to_value(
400
+ self,
401
+ concept: Concept,
402
+ local_concepts: dict[str, Concept] | None = None,
403
+ ) -> Any:
404
+ if not concept.granularity == Granularity.SINGLE_ROW:
405
+ raise SyntaxError(f"Cannot bind non-singleton concept {concept.address}")
406
+ # TODO: to get rid of function here - need to figure out why it's getting passed in
407
+ if (
408
+ isinstance(concept.lineage, (BuildFunction, Function))
409
+ and concept.lineage.operator == FunctionType.CONSTANT
410
+ ):
411
+ rval = concept.lineage.arguments[0]
412
+ if isinstance(rval, ListWrapper):
413
+ return [x for x in rval]
414
+ if isinstance(rval, MapWrapper):
415
+ return {k: v for k, v in rval.items()}
416
+ # if isinstance(rval, ConceptRef):
417
+ # return self._concept_to_value(self.environment.concepts[rval.address], local_concepts=local_concepts)
418
+ return rval
419
+ else:
420
+ results = self.execute_query(f"select {concept.name} limit 1;").fetchone()
421
+ if not results:
422
+ return None
423
+ return results[0]
424
+
397
425
  def _hydrate_param(
398
426
  self, param: str, local_concepts: dict[str, Concept] | None = None
399
427
  ) -> Any:
@@ -412,23 +440,7 @@ class Executor(object):
412
440
  raise SyntaxError(f"No concept found for parameter {param}")
413
441
 
414
442
  concept: Concept = matched.pop()
415
- if not concept.granularity == Granularity.SINGLE_ROW:
416
- raise SyntaxError(f"Cannot bind non-singleton concept {concept.address}")
417
- if (
418
- isinstance(concept.lineage, Function)
419
- and concept.lineage.operator == FunctionType.CONSTANT
420
- ):
421
- rval = concept.lineage.arguments[0]
422
- if isinstance(rval, ListWrapper):
423
- return [x for x in rval]
424
- if isinstance(rval, MapWrapper):
425
- return {k: v for k, v in rval.items()}
426
- return rval
427
- else:
428
- results = self.execute_query(f"select {concept.name} limit 1;").fetchone()
429
- if not results:
430
- return None
431
- return results[0]
443
+ return self._concept_to_value(concept, local_concepts=local_concepts)
432
444
 
433
445
  def execute_raw_sql(
434
446
  self,
@@ -437,7 +449,7 @@ class Executor(object):
437
449
  local_concepts: dict[str, Concept] | None = None,
438
450
  ) -> CursorResult:
439
451
  """Run a command against the raw underlying
440
- execution engine"""
452
+ execution engine."""
441
453
  final_params = None
442
454
  q = text(command)
443
455
  if variables:
@@ -459,7 +471,7 @@ class Executor(object):
459
471
  def execute_text(
460
472
  self, command: str, non_interactive: bool = False
461
473
  ) -> List[CursorResult]:
462
- """Run a preql text command"""
474
+ """Run a trilogy query expressed as text."""
463
475
  output = []
464
476
  # connection = self.engine.connect()
465
477
  for statement in self.parse_text_generator(command):
@@ -1,13 +1,15 @@
1
- from trilogy.core.models import (
1
+ from trilogy.core.models.execute import (
2
2
  CTE,
3
+ QueryDatasource,
4
+ UnionCTE,
5
+ )
6
+ from trilogy.core.processing.nodes import StrategyNode
7
+ from trilogy.core.statements.author import (
3
8
  MultiSelectStatement,
4
9
  PersistStatement,
5
- QueryDatasource,
6
10
  RowsetDerivationStatement,
7
11
  SelectStatement,
8
- UnionCTE,
9
12
  )
10
- from trilogy.core.processing.nodes import StrategyNode
11
13
 
12
14
 
13
15
  class BaseHook:
@@ -1,16 +1,17 @@
1
1
  from enum import Enum
2
2
  from logging import DEBUG, StreamHandler
3
3
  from typing import Union
4
+ from uuid import uuid4
4
5
 
5
6
  from trilogy.constants import logger
6
- from trilogy.core.models import (
7
+ from trilogy.core.models.build import BuildDatasource
8
+ from trilogy.core.models.execute import (
7
9
  CTE,
8
- Datasource,
9
10
  QueryDatasource,
10
- SelectStatement,
11
11
  UnionCTE,
12
12
  )
13
13
  from trilogy.core.processing.nodes import StrategyNode
14
+ from trilogy.core.statements.author import SelectStatement
14
15
  from trilogy.dialect.bigquery import BigqueryDialect
15
16
  from trilogy.hooks.base_hook import BaseHook
16
17
 
@@ -24,90 +25,6 @@ class PrintMode(Enum):
24
25
  renderer = BigqueryDialect()
25
26
 
26
27
 
27
- def print_recursive_resolved(
28
- input: Union[QueryDatasource, Datasource], mode: PrintMode, depth: int = 0
29
- ):
30
- extra = []
31
- if isinstance(input, QueryDatasource):
32
- if input.joins:
33
- extra.append("join")
34
- if input.condition:
35
- extra.append("filter")
36
- if input.group_required:
37
- extra.append("group")
38
- output = [c.address for c in input.output_concepts[:3]]
39
- if len(input.output_concepts) > 3:
40
- output.append("...")
41
- display = [
42
- (
43
- " " * depth,
44
- input.__class__.__name__,
45
- "<",
46
- ",".join(extra),
47
- ">",
48
- # [c.address for c in input.input_concepts],
49
- "->",
50
- output,
51
- )
52
- ]
53
- if isinstance(input, QueryDatasource):
54
- for child in input.datasources:
55
- display += print_recursive_resolved(child, mode=mode, depth=depth + 1)
56
- return display
57
-
58
-
59
- def print_recursive_nodes(
60
- input: StrategyNode, mode: PrintMode = PrintMode.BASIC, depth: int = 0
61
- ):
62
- resolved = input.resolve()
63
- if mode == PrintMode.FULL:
64
- display = [
65
- [
66
- " " * depth,
67
- input,
68
- "->",
69
- resolved.grain,
70
- "->",
71
- [c.address for c in resolved.output_concepts],
72
- ]
73
- ]
74
- elif mode == PrintMode.BASIC:
75
- display = [
76
- [
77
- " " * depth,
78
- input,
79
- "->",
80
- resolved.grain,
81
- ]
82
- ]
83
- for child in input.parents:
84
- display += print_recursive_nodes(
85
- child,
86
- mode=mode,
87
- depth=depth + 1,
88
- )
89
- return display
90
-
91
-
92
- def print_recursive_ctes(
93
- input: CTE | UnionCTE, depth: int = 0, max_depth: int | None = None
94
- ):
95
- if max_depth and depth > max_depth:
96
- return
97
- select_statement = [c.address for c in input.output_columns]
98
- print(" " * depth, input.name, "->", input.group_to_grain, "->", select_statement)
99
- sql = renderer.render_cte(input).statement
100
- for line in sql.split("\n"):
101
- logger.debug(" " * (depth) + line)
102
- if isinstance(input, CTE):
103
- for child in input.parent_ctes:
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)
109
-
110
-
111
28
  class DebuggingHook(BaseHook):
112
29
  def __init__(
113
30
  self,
@@ -127,24 +44,124 @@ class DebuggingHook(BaseHook):
127
44
  self.process_nodes = PrintMode(process_nodes)
128
45
  self.process_datasources = PrintMode(process_datasources)
129
46
  self.process_other = PrintMode(process_other)
47
+ self.messages: list[str] = []
48
+ self.uuid = uuid4()
49
+
50
+ def print(self, *args):
51
+ merged = " ".join([str(x) for x in args])
52
+ self.messages.append(merged)
53
+
54
+ def write(self):
55
+ with open(f"debug_{self.uuid}.log", "w") as f:
56
+ f.write("\n".join(self.messages))
130
57
 
131
58
  def process_select_info(self, select: SelectStatement):
132
59
  if self.process_datasources != PrintMode.OFF:
133
- print(f"grain: {str(select.grain)}")
60
+ self.print(f"grain: {str(select.grain)}")
134
61
 
135
62
  def process_root_datasource(self, datasource: QueryDatasource):
136
63
  if self.process_datasources != PrintMode.OFF:
137
- printed = print_recursive_resolved(datasource, self.process_datasources)
64
+ printed = self.print_recursive_resolved(
65
+ datasource, self.process_datasources
66
+ )
138
67
  for row in printed:
139
- print("".join([str(v) for v in row]))
68
+ self.print("".join([str(v) for v in row]))
140
69
 
141
70
  def process_root_cte(self, cte: CTE | UnionCTE):
142
71
  if self.process_ctes != PrintMode.OFF:
143
- print_recursive_ctes(cte, max_depth=self.max_depth)
72
+ self.print_recursive_ctes(cte, max_depth=self.max_depth)
144
73
 
145
74
  def process_root_strategy_node(self, node: StrategyNode):
146
75
  if self.process_nodes != PrintMode.OFF:
147
- printed = print_recursive_nodes(node, mode=self.process_nodes)
76
+ printed = self.print_recursive_nodes(node, mode=self.process_nodes)
148
77
  for row in printed:
149
78
  # logger.info("".join([str(v) for v in row]))
150
- print("".join([str(v) for v in row]))
79
+ self.print("".join([str(v) for v in row]))
80
+
81
+ def print_recursive_resolved(
82
+ self,
83
+ input: Union[QueryDatasource, BuildDatasource],
84
+ mode: PrintMode,
85
+ depth: int = 0,
86
+ ):
87
+ extra = []
88
+ if isinstance(input, QueryDatasource):
89
+ if input.joins:
90
+ extra.append("join")
91
+ if input.condition:
92
+ extra.append("filter")
93
+ if input.group_required:
94
+ extra.append("group")
95
+ output = [c.address for c in input.output_concepts[:3]]
96
+ if len(input.output_concepts) > 3:
97
+ output.append("...")
98
+ display = [
99
+ (
100
+ " " * depth,
101
+ input.__class__.__name__,
102
+ "<",
103
+ ",".join(extra),
104
+ ">",
105
+ # [c.address for c in input.input_concepts],
106
+ "->",
107
+ output,
108
+ )
109
+ ]
110
+ if isinstance(input, QueryDatasource):
111
+ for child in input.datasources:
112
+ display += self.print_recursive_resolved(
113
+ child, mode=mode, depth=depth + 1
114
+ )
115
+ return display
116
+
117
+ def print_recursive_ctes(
118
+ self, input: CTE | UnionCTE, depth: int = 0, max_depth: int | None = None
119
+ ):
120
+ if max_depth and depth > max_depth:
121
+ return
122
+ select_statement = [c.address for c in input.output_columns]
123
+ self.print(
124
+ " " * depth, input.name, "->", input.group_to_grain, "->", select_statement
125
+ )
126
+ sql = renderer.render_cte(input).statement
127
+ for line in sql.split("\n"):
128
+ logger.debug(" " * (depth) + line)
129
+ if isinstance(input, CTE):
130
+ for child in input.parent_ctes:
131
+ self.print_recursive_ctes(child, depth + 1)
132
+ elif isinstance(input, UnionCTE):
133
+ for child in input.parent_ctes:
134
+ for parent in child.parent_ctes:
135
+ self.print_recursive_ctes(parent, depth + 1)
136
+
137
+ def print_recursive_nodes(
138
+ self, input: StrategyNode, mode: PrintMode = PrintMode.BASIC, depth: int = 0
139
+ ):
140
+ resolved = input.resolve()
141
+ if mode == PrintMode.FULL:
142
+ display = [
143
+ [
144
+ " " * depth,
145
+ input,
146
+ "->",
147
+ resolved.grain,
148
+ "->",
149
+ [c.address for c in resolved.output_concepts],
150
+ ]
151
+ ]
152
+ elif mode == PrintMode.BASIC:
153
+ display = [
154
+ [
155
+ " " * depth,
156
+ input,
157
+ "->",
158
+ resolved.grain,
159
+ ]
160
+ ]
161
+ for child in input.parents:
162
+ display += self.print_recursive_nodes(
163
+ child,
164
+ mode=mode,
165
+ depth=depth + 1,
166
+ )
167
+ return display
trilogy/parser.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from trilogy.core.models import Environment
3
+ from trilogy.core.models.environment import Environment
4
4
  from trilogy.parsing.parse_engine import parse_text
5
5
 
6
6