pytrilogy 0.0.1.104__py3-none-any.whl → 0.0.1.106__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 (32) hide show
  1. {pytrilogy-0.0.1.104.dist-info → pytrilogy-0.0.1.106.dist-info}/METADATA +1 -1
  2. {pytrilogy-0.0.1.104.dist-info → pytrilogy-0.0.1.106.dist-info}/RECORD +32 -31
  3. trilogy/__init__.py +3 -2
  4. trilogy/constants.py +1 -0
  5. trilogy/core/models.py +226 -49
  6. trilogy/core/optimization.py +141 -0
  7. trilogy/core/processing/concept_strategies_v3.py +1 -0
  8. trilogy/core/processing/node_generators/common.py +19 -7
  9. trilogy/core/processing/node_generators/filter_node.py +37 -10
  10. trilogy/core/processing/node_generators/merge_node.py +11 -1
  11. trilogy/core/processing/nodes/base_node.py +4 -2
  12. trilogy/core/processing/nodes/group_node.py +5 -2
  13. trilogy/core/processing/nodes/merge_node.py +13 -8
  14. trilogy/core/query_processor.py +5 -2
  15. trilogy/dialect/base.py +85 -54
  16. trilogy/dialect/bigquery.py +6 -4
  17. trilogy/dialect/common.py +8 -6
  18. trilogy/dialect/config.py +69 -1
  19. trilogy/dialect/duckdb.py +5 -4
  20. trilogy/dialect/enums.py +40 -19
  21. trilogy/dialect/postgres.py +4 -2
  22. trilogy/dialect/presto.py +6 -4
  23. trilogy/dialect/snowflake.py +6 -4
  24. trilogy/dialect/sql_server.py +4 -1
  25. trilogy/executor.py +18 -5
  26. trilogy/parsing/common.py +30 -0
  27. trilogy/parsing/parse_engine.py +43 -83
  28. trilogy/parsing/render.py +0 -122
  29. {pytrilogy-0.0.1.104.dist-info → pytrilogy-0.0.1.106.dist-info}/LICENSE.md +0 -0
  30. {pytrilogy-0.0.1.104.dist-info → pytrilogy-0.0.1.106.dist-info}/WHEEL +0 -0
  31. {pytrilogy-0.0.1.104.dist-info → pytrilogy-0.0.1.106.dist-info}/entry_points.txt +0 -0
  32. {pytrilogy-0.0.1.104.dist-info → pytrilogy-0.0.1.106.dist-info}/top_level.txt +0 -0
trilogy/parsing/common.py CHANGED
@@ -174,3 +174,33 @@ def agg_wrapper_to_concept(
174
174
  keys=tuple(parent.by) if parent.by else keys,
175
175
  )
176
176
  return out
177
+
178
+
179
+ def arbitrary_to_concept(
180
+ parent: (
181
+ AggregateWrapper
182
+ | WindowItem
183
+ | FilterItem
184
+ | Function
185
+ | ListWrapper
186
+ | int
187
+ | float
188
+ | str
189
+ ),
190
+ namespace: str,
191
+ name: str,
192
+ metadata: Metadata | None = None,
193
+ purpose: Purpose | None = None,
194
+ ) -> Concept:
195
+ if isinstance(parent, AggregateWrapper):
196
+ return agg_wrapper_to_concept(parent, namespace, name, metadata, purpose)
197
+ elif isinstance(parent, WindowItem):
198
+ return window_item_to_concept(parent, name, namespace, purpose, metadata)
199
+ elif isinstance(parent, FilterItem):
200
+ return filter_item_to_concept(parent, name, namespace, purpose, metadata)
201
+ elif isinstance(parent, Function):
202
+ return function_to_concept(parent, name, namespace)
203
+ elif isinstance(parent, ListWrapper):
204
+ return constant_to_concept(parent, name, namespace, purpose, metadata)
205
+ else:
206
+ return constant_to_concept(parent, name, namespace, purpose, metadata)
@@ -17,6 +17,7 @@ from trilogy.constants import (
17
17
  DEFAULT_NAMESPACE,
18
18
  NULL_VALUE,
19
19
  VIRTUAL_CONCEPT_PREFIX,
20
+ MagicConstants,
20
21
  )
21
22
  from trilogy.core.enums import (
22
23
  BooleanOperator,
@@ -62,6 +63,7 @@ from trilogy.core.models import (
62
63
  ColumnAssignment,
63
64
  Comment,
64
65
  Comparison,
66
+ SubselectComparison,
65
67
  Concept,
66
68
  ConceptTransform,
67
69
  Conditional,
@@ -108,6 +110,7 @@ from trilogy.parsing.common import (
108
110
  function_to_concept,
109
111
  filter_item_to_concept,
110
112
  constant_to_concept,
113
+ arbitrary_to_concept,
111
114
  )
112
115
 
113
116
  CONSTANT_TYPES = (int, float, str, bool, ListWrapper)
@@ -187,9 +190,9 @@ grammar = r"""
187
190
 
188
191
  // FUNCTION blocks
189
192
  function: raw_function
190
- function_binding_item: IDENTIFIER data_type
193
+ function_binding_item: IDENTIFIER ":" data_type
191
194
  function_binding_list: (function_binding_item ",")* function_binding_item ","?
192
- raw_function: "def" "rawsql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
195
+ raw_function: "bind" "sql" IDENTIFIER "(" function_binding_list ")" "-" ">" data_type "as"i MULTILINE_STRING
193
196
 
194
197
 
195
198
  // user_id where state = Mexico
@@ -246,7 +249,9 @@ grammar = r"""
246
249
 
247
250
  COMPARISON_OPERATOR: (/is[\s]+not/ | "is" |"=" | ">" | "<" | ">=" | "<=" | "!=" )
248
251
 
249
- comparison: (expr COMPARISON_OPERATOR expr) | (expr array_comparison expr_tuple)
252
+ comparison: (expr COMPARISON_OPERATOR expr) | (expr array_comparison expr_tuple)
253
+
254
+ subselect_comparison: expr array_comparison expr
250
255
 
251
256
  expr_tuple: "(" (expr ",")* expr ","? ")"
252
257
 
@@ -258,7 +263,7 @@ grammar = r"""
258
263
 
259
264
  parenthetical: "(" (conditional | expr) ")"
260
265
 
261
- expr: window_item | filter_item | comparison | fgroup | aggregate_functions | unnest | _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions | literal | expr_reference | index_access | attr_access | parenthetical
266
+ expr: window_item | filter_item | comparison | subselect_comparison | fgroup | aggregate_functions | unnest | _string_functions | _math_functions | _generic_functions | _constant_functions| _date_functions | literal | expr_reference | index_access | attr_access | parenthetical
262
267
 
263
268
  // functions
264
269
 
@@ -525,43 +530,11 @@ class ParseToObjects(Transformer):
525
530
  concept.metadata.line_number = meta.line
526
531
  self.environment.add_concept(concept, meta=meta)
527
532
  final.append(concept)
528
- elif isinstance(arg, FilterItem):
533
+ elif isinstance(
534
+ arg, (FilterItem, WindowItem, AggregateWrapper, ListWrapper)
535
+ ):
529
536
  id_hash = string_to_hash(str(arg))
530
- concept = filter_item_to_concept(
531
- arg,
532
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
533
- namespace=self.environment.namespace,
534
- )
535
- if concept.metadata:
536
- concept.metadata.line_number = meta.line
537
- self.environment.add_concept(concept, meta=meta)
538
- final.append(concept)
539
- elif isinstance(arg, WindowItem):
540
- id_hash = string_to_hash(str(arg))
541
- concept = window_item_to_concept(
542
- arg,
543
- namespace=self.environment.namespace,
544
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
545
- )
546
- if concept.metadata:
547
- concept.metadata.line_number = meta.line
548
- self.environment.add_concept(concept, meta=meta)
549
- final.append(concept)
550
- elif isinstance(arg, AggregateWrapper):
551
- id_hash = string_to_hash(str(arg))
552
- concept = agg_wrapper_to_concept(
553
- arg,
554
- namespace=self.environment.namespace,
555
- name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
556
- )
557
- if concept.metadata:
558
- concept.metadata.line_number = meta.line
559
- self.environment.add_concept(concept, meta=meta)
560
- final.append(concept)
561
- # we don't need virtual types for most constants
562
- elif isinstance(arg, (ListWrapper)):
563
- id_hash = string_to_hash(str(arg))
564
- concept = constant_to_concept(
537
+ concept = arbitrary_to_concept(
565
538
  arg,
566
539
  name=f"{VIRTUAL_CONCEPT_PREFIX}_{id_hash}",
567
540
  namespace=self.environment.namespace,
@@ -570,6 +543,7 @@ class ParseToObjects(Transformer):
570
543
  concept.metadata.line_number = meta.line
571
544
  self.environment.add_concept(concept, meta=meta)
572
545
  final.append(concept)
546
+
573
547
  else:
574
548
  final.append(arg)
575
549
  return final
@@ -773,8 +747,10 @@ class ParseToObjects(Transformer):
773
747
  while isinstance(source_value, Parenthetical):
774
748
  source_value = source_value.content
775
749
 
776
- if isinstance(source_value, FilterItem):
777
- concept = filter_item_to_concept(
750
+ if isinstance(
751
+ source_value, (FilterItem, WindowItem, AggregateWrapper, Function)
752
+ ):
753
+ concept = arbitrary_to_concept(
778
754
  source_value,
779
755
  name=name,
780
756
  namespace=namespace,
@@ -782,31 +758,6 @@ class ParseToObjects(Transformer):
782
758
  metadata=metadata,
783
759
  )
784
760
 
785
- if concept.metadata:
786
- concept.metadata.line_number = meta.line
787
- self.environment.add_concept(concept, meta=meta)
788
- return ConceptDerivation(concept=concept)
789
- elif isinstance(source_value, WindowItem):
790
-
791
- concept = window_item_to_concept(
792
- source_value,
793
- name=name,
794
- namespace=namespace,
795
- purpose=purpose,
796
- metadata=metadata,
797
- )
798
- if concept.metadata:
799
- concept.metadata.line_number = meta.line
800
- self.environment.add_concept(concept, meta=meta)
801
- return ConceptDerivation(concept=concept)
802
- elif isinstance(source_value, AggregateWrapper):
803
- concept = agg_wrapper_to_concept(
804
- source_value,
805
- namespace=namespace,
806
- name=name,
807
- metadata=metadata,
808
- purpose=purpose,
809
- )
810
761
  if concept.metadata:
811
762
  concept.metadata.line_number = meta.line
812
763
  self.environment.add_concept(concept, meta=meta)
@@ -824,19 +775,6 @@ class ParseToObjects(Transformer):
824
775
  self.environment.add_concept(concept, meta=meta)
825
776
  return ConceptDerivation(concept=concept)
826
777
 
827
- elif isinstance(source_value, Function):
828
- function: Function = source_value
829
-
830
- concept = function_to_concept(
831
- function,
832
- name=name,
833
- namespace=namespace,
834
- )
835
- if concept.metadata:
836
- concept.metadata.line_number = meta.line
837
- self.environment.add_concept(concept, meta=meta)
838
- return ConceptDerivation(concept=concept)
839
-
840
778
  raise SyntaxError(
841
779
  f"Received invalid type {type(args[2])} {args[2]} as input to select"
842
780
  " transform"
@@ -926,7 +864,7 @@ class ParseToObjects(Transformer):
926
864
  elif isinstance(val, Grain):
927
865
  grain = val
928
866
  elif isinstance(val, Query):
929
- address = Address(location=f"({val.text})")
867
+ address = Address(location=f"({val.text})", is_query=True)
930
868
  if not address:
931
869
  raise ValueError(
932
870
  "Malformed datasource, missing address or query declaration"
@@ -1219,7 +1157,14 @@ class ParseToObjects(Transformer):
1219
1157
  def where(self, args):
1220
1158
  root = args[0]
1221
1159
  if not isinstance(root, (Comparison, Conditional, Parenthetical)):
1222
- root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1160
+ if arg_to_datatype(root) == DataType.BOOL:
1161
+ root = Comparison(left=root, right=True, operator=ComparisonOperator.EQ)
1162
+ else:
1163
+ root = Comparison(
1164
+ left=root,
1165
+ right=MagicConstants.NULL,
1166
+ operator=ComparisonOperator.IS_NOT,
1167
+ )
1223
1168
  return WhereClause(conditional=root)
1224
1169
 
1225
1170
  @v_args(meta=True)
@@ -1232,7 +1177,6 @@ class ParseToObjects(Transformer):
1232
1177
 
1233
1178
  @v_args(meta=True)
1234
1179
  def raw_function(self, meta: Meta, args) -> Function:
1235
- print(args)
1236
1180
  identity = args[0]
1237
1181
  fargs = args[1]
1238
1182
  output = args[2]
@@ -1273,6 +1217,22 @@ class ParseToObjects(Transformer):
1273
1217
  def comparison(self, args) -> Comparison:
1274
1218
  return Comparison(left=args[0], right=args[2], operator=args[1])
1275
1219
 
1220
+ @v_args(meta=True)
1221
+ def subselect_comparison(self, meta: Meta, args) -> SubselectComparison:
1222
+ right = args[2]
1223
+ if not isinstance(right, Concept):
1224
+ right = arbitrary_to_concept(
1225
+ right,
1226
+ namespace=self.environment.namespace,
1227
+ name=f"{VIRTUAL_CONCEPT_PREFIX}_{string_to_hash(str(right))}",
1228
+ )
1229
+ self.environment.add_concept(right)
1230
+ return SubselectComparison(
1231
+ left=args[0],
1232
+ right=right,
1233
+ operator=args[1],
1234
+ )
1235
+
1276
1236
  def expr_tuple(self, args):
1277
1237
  return Parenthetical(content=args)
1278
1238
 
trilogy/parsing/render.py CHANGED
@@ -298,128 +298,6 @@ class Renderer:
298
298
  def _(self, arg: "FilterItem"):
299
299
  return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
300
300
 
301
- @to_string.register
302
- def _(self, arg: "WindowItem"):
303
- over = ",".join(self.to_string(c) for c in arg.over)
304
- order = ",".join(self.to_string(c) for c in arg.order_by)
305
- if over:
306
- return (
307
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
308
- )
309
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
310
-
311
- @to_string.register
312
- def _(self, arg: "FilterItem"):
313
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
314
-
315
- @to_string.register
316
- def _(self, arg: "ImportStatement"):
317
- return f"import {arg.path} as {arg.alias};"
318
-
319
- @to_string.register
320
- def _(self, arg: "WindowItem"):
321
- over = ",".join(self.to_string(c) for c in arg.over)
322
- order = ",".join(self.to_string(c) for c in arg.order_by)
323
- if over:
324
- return (
325
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
326
- )
327
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
328
-
329
- @to_string.register
330
- def _(self, arg: "FilterItem"):
331
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
332
-
333
- @to_string.register
334
- def _(self, arg: "ImportStatement"):
335
- return f"import {arg.path} as {arg.alias};"
336
-
337
- @to_string.register
338
- def _(self, arg: "WindowItem"):
339
- over = ",".join(self.to_string(c) for c in arg.over)
340
- order = ",".join(self.to_string(c) for c in arg.order_by)
341
- if over:
342
- return (
343
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
344
- )
345
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
346
-
347
- @to_string.register
348
- def _(self, arg: "FilterItem"):
349
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
350
-
351
- @to_string.register
352
- def _(self, arg: "ImportStatement"):
353
- return f"import {arg.path} as {arg.alias};"
354
-
355
- @to_string.register
356
- def _(self, arg: "WindowItem"):
357
- over = ",".join(self.to_string(c) for c in arg.over)
358
- order = ",".join(self.to_string(c) for c in arg.order_by)
359
- if over:
360
- return (
361
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
362
- )
363
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
364
-
365
- @to_string.register
366
- def _(self, arg: "FilterItem"):
367
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
368
-
369
- @to_string.register
370
- def _(self, arg: "ImportStatement"):
371
- return f"import {arg.path} as {arg.alias};"
372
-
373
- @to_string.register
374
- def _(self, arg: "WindowItem"):
375
- over = ",".join(self.to_string(c) for c in arg.over)
376
- order = ",".join(self.to_string(c) for c in arg.order_by)
377
- if over:
378
- return (
379
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
380
- )
381
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
382
-
383
- @to_string.register
384
- def _(self, arg: "FilterItem"):
385
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
386
-
387
- @to_string.register
388
- def _(self, arg: "ImportStatement"):
389
- return f"import {arg.path} as {arg.alias};"
390
-
391
- @to_string.register
392
- def _(self, arg: "WindowItem"):
393
- over = ",".join(self.to_string(c) for c in arg.over)
394
- order = ",".join(self.to_string(c) for c in arg.order_by)
395
- if over:
396
- return (
397
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
398
- )
399
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
400
-
401
- @to_string.register
402
- def _(self, arg: "FilterItem"):
403
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
404
-
405
- @to_string.register
406
- def _(self, arg: "ImportStatement"):
407
- return f"import {arg.path} as {arg.alias};"
408
-
409
- @to_string.register
410
- def _(self, arg: "WindowItem"):
411
- over = ",".join(self.to_string(c) for c in arg.over)
412
- order = ",".join(self.to_string(c) for c in arg.order_by)
413
- if over:
414
- return (
415
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
416
- )
417
- return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
418
-
419
- @to_string.register
420
- def _(self, arg: "FilterItem"):
421
- return f"filter {self.to_string(arg.content)} where {self.to_string(arg.where)}"
422
-
423
301
  @to_string.register
424
302
  def _(self, arg: "ImportStatement"):
425
303
  return f"import {arg.path} as {arg.alias};"