pytrilogy 0.0.3.79__py3-none-any.whl → 0.0.3.81__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.79
3
+ Version: 0.0.3.81
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,5 +1,5 @@
1
- pytrilogy-0.0.3.79.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=YY8Yc29Dawl2U9Xu5dC8ZXP8Q0yEh7iv4k_Neu-Nc3c,303
1
+ pytrilogy-0.0.3.81.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=TMYRdrnDsNt6dIWHpcXgKPBKzjBkbMC5oJTgTrClxz0,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  trilogy/constants.py,sha256=eKb_EJvSqjN9tGbdVEViwdtwwh8fZ3-jpOEDqL71y70,1691
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
@@ -11,24 +11,24 @@ trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
11
11
  trilogy/authoring/__init__.py,sha256=e74k-Jep4DLYPQU_2m0aVsYlw5HKTOucAKtdTbd6f2g,2595
12
12
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
14
- trilogy/core/enums.py,sha256=tDUXVWxvWgnO1u7LLS0iIReeHYbyFLN2ZbpTdUyKdjE,8060
14
+ trilogy/core/enums.py,sha256=kuwTeEur5TZ07DX8nO3fSiRQ32bLZ5jRK-vIjHbBUnc,8118
15
15
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
16
16
  trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
17
17
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
18
  trilogy/core/exceptions.py,sha256=jYEduuMehcMkmCpf-OC_taELPZm7qNfeSNzIWkDYScs,707
19
- trilogy/core/functions.py,sha256=wQ5yCvMKXSCkaSo7yiwlGY0wvqE4Sm2MI9e5NEr2ORw,30933
19
+ trilogy/core/functions.py,sha256=9Q-c7UGxOR0SVwzcHt9mHX_aVroW9jEWwJr7BtHigw8,31766
20
20
  trilogy/core/graph_models.py,sha256=BYhJzHKSgnZHVLJs1CfsgrxTPHqKqPNeA64RlozGY0A,3498
21
21
  trilogy/core/internal.py,sha256=wFx4e1I0mtx159YFShSXeUBSQ82NINtAbOI-92RX4i8,2151
22
22
  trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
23
23
  trilogy/core/query_processor.py,sha256=5aFgv-2LVM1Uku9cR_tFuTRDwyLnxc95bCMAHeFy2AY,20332
24
24
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
25
25
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- trilogy/core/models/author.py,sha256=9gQ2UBJ2qZ4ngFOXei4VbYQAp8UTrcAr_5GvWW2O2l8,78192
26
+ trilogy/core/models/author.py,sha256=Xt_TRVl4xHF6fe-KTKPSt4Z8Srmplnv2KQe2grZXX3w,78411
27
27
  trilogy/core/models/build.py,sha256=0TRcL__hvajLcpFwYeLjCV35cow5oVPbJHXA3oO09XA,65931
28
28
  trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
29
29
  trilogy/core/models/core.py,sha256=q-8ExAaSB3PoSDX2XOqfZ01HBBvT1pZFs7jGg1vZizc,11096
30
30
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
31
- trilogy/core/models/environment.py,sha256=TBbPfsXHpJK49QKuqHwhgZD4PwHiSAYjXmTTTomRE7o,27861
31
+ trilogy/core/models/environment.py,sha256=0IHSCFf5e5b4LPQN3vmjumtfM1iD1tN4WMoUr0UqxZI,27855
32
32
  trilogy/core/models/execute.py,sha256=Erufst3yroU3PgNJMWF5lICm14AkH02laYeesSB01UU,41705
33
33
  trilogy/core/optimizations/__init__.py,sha256=YH2-mGXZnVDnBcWVi8vTbrdw7Qs5TivG4h38rH3js_I,290
34
34
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
@@ -43,13 +43,13 @@ trilogy/core/processing/discovery_validation.py,sha256=fGWJmKpgEd1f4RkK-fYOBUT1c
43
43
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
44
44
  trilogy/core/processing/utility.py,sha256=xsuVMRFi2nY2So0yohhweI2D92wZVsTpHezS0giM4ck,22583
45
45
  trilogy/core/processing/node_generators/__init__.py,sha256=w8TQQgNhyAra6JQHdg1_Ags4BGyxjXYruu6UeC5yOkI,873
46
- trilogy/core/processing/node_generators/basic_node.py,sha256=luN8LftafZepoFgDRv4gmvEGFlOI2j0icJ5fz4UT7uo,5165
46
+ trilogy/core/processing/node_generators/basic_node.py,sha256=TLZCv4WS196a-0g5xgKuJGthnGP8Ugm46iz85_3NIY4,5626
47
47
  trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
48
48
  trilogy/core/processing/node_generators/filter_node.py,sha256=oRRq2-T3ufgn4D23uQsc58f20eFk-djs4QI3WKA75K8,10908
49
49
  trilogy/core/processing/node_generators/group_node.py,sha256=1QJhRxsTklJ5xq8wHlAURZaN9gL9FPpeCa1OJ7IwXnY,6769
50
50
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
51
51
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=GWV5yLmKTe1yyPhN60RG1Rnrn4ktfn9lYYXi_FVU4UI,7061
52
- trilogy/core/processing/node_generators/node_merge_node.py,sha256=KtTuvL9-xaTV_ZUO1DUyz2Rd595QfD9SvOv5filCDy0,17428
52
+ trilogy/core/processing/node_generators/node_merge_node.py,sha256=bYhXGJqJDgT4JWlN4u761V3bkP96o6J25LFs6YBOiKQ,18304
53
53
  trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
54
54
  trilogy/core/processing/node_generators/rowset_node.py,sha256=5L5u6xz1In8EaHQdcYgR2si-tz9WB9YLXURo4AkUT9A,6630
55
55
  trilogy/core/processing/node_generators/select_merge_node.py,sha256=Cv2GwNiYSmwewjuK8T3JB3pbgrLZFPsB75DCP153BMA,22818
@@ -76,12 +76,12 @@ trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
76
76
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
77
77
  trilogy/core/statements/execute.py,sha256=pfr1CZ_Cx1qQ-7LDyRI0JUfvtxBr_GGv-VeqiAjr43g,1406
78
78
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
- trilogy/dialect/base.py,sha256=aLJ7-Lq7TTe7I_FjMzhkC5LfrqyjN55AFD8T0lqsiRs,47202
79
+ trilogy/dialect/base.py,sha256=0nVDvsnDS8iMTIlCUsBs_ymoZMW6uh04U4MMZxOuHUw,47428
80
80
  trilogy/dialect/bigquery.py,sha256=8xhEu0z_lKANjbvzvBbC7CeKrJf1iP8YyrHqNale-ug,4351
81
81
  trilogy/dialect/common.py,sha256=tSthIZOXXRPQ4KeMKnDDsH7KlTmf2EVqigVtLyoc4zc,6071
82
82
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
83
83
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
84
- trilogy/dialect/duckdb.py,sha256=0qWQkyijz1_MnsVFyY_Iqqx0ba6E8o50s8pvLLpZH6g,5138
84
+ trilogy/dialect/duckdb.py,sha256=uqnZEd1IH4tO8FSbbUXe3Jl9EjoyrDde406k0VDuMXw,5544
85
85
  trilogy/dialect/enums.py,sha256=FRNYQ5-w-B6-X0yXKNU5g9GowsMlERFogTC5u2nxL_s,4740
86
86
  trilogy/dialect/postgres.py,sha256=el2PKwfyvWGk5EZtLudqAH5ewLitY1sFHJiocBSyxyM,3393
87
87
  trilogy/dialect/presto.py,sha256=yzSF8SZ6o1dizj1UueAa7S3lR0qNYJdSXbF78EHyhY0,3668
@@ -97,9 +97,9 @@ trilogy/parsing/common.py,sha256=yV1AckK0h8u1OFeGQBTMu-wuW5m63c5CcZuPicsTH_w,306
97
97
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
98
98
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
99
99
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
100
- trilogy/parsing/parse_engine.py,sha256=xtwWwe7EfYcmt9_lwJ0JH0AhKVcPHty97VJCpyM5beA,78068
100
+ trilogy/parsing/parse_engine.py,sha256=ll3uBwpJ2iAxT-KbbXy7OuCqUvKfLPzB1gHr8gApv_s,78591
101
101
  trilogy/parsing/render.py,sha256=HSNntD82GiiwHT-TWPLXAaIMWLYVV5B5zQEsgwrHIBE,19605
102
- trilogy/parsing/trilogy.lark,sha256=wiGXJdKfPTG7E_XdkN1rf9g9Yy1-UMVAXyTxtrBPm9w,15037
102
+ trilogy/parsing/trilogy.lark,sha256=Uhy6A00-GsC-L8fRCN0JZC6fL87ujOOd9_4LbQWApY4,15248
103
103
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
104
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
105
105
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -110,8 +110,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
110
110
  trilogy/std/net.preql,sha256=7l7MqIjs6TDCpO6dBAoNJU81Ex255jZRK36kBgE1GDs,158
111
111
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
112
112
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
113
- pytrilogy-0.0.3.79.dist-info/METADATA,sha256=-WUI1LfhYvSwypFjkJOGe4KiAgTReZ4w-dcEB7L5J3E,9589
114
- pytrilogy-0.0.3.79.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
- pytrilogy-0.0.3.79.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
116
- pytrilogy-0.0.3.79.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
117
- pytrilogy-0.0.3.79.dist-info/RECORD,,
113
+ pytrilogy-0.0.3.81.dist-info/METADATA,sha256=0A4NkUsyAunmuRlrFbsAPi8sksv9Yf0l4jAFh6Q_NeU,9589
114
+ pytrilogy-0.0.3.81.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
+ pytrilogy-0.0.3.81.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
116
+ pytrilogy-0.0.3.81.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
117
+ pytrilogy-0.0.3.81.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.79"
7
+ __version__ = "0.0.3.81"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/core/enums.py CHANGED
@@ -171,6 +171,7 @@ class FunctionType(Enum):
171
171
  RANDOM = "random"
172
172
  FLOOR = "floor"
173
173
  CEIL = "ceil"
174
+ LOG = "log"
174
175
 
175
176
  # Aggregates
176
177
  ## group is not a real aggregate - it just means group by this + some other set of fields
@@ -193,6 +194,8 @@ class FunctionType(Enum):
193
194
  SUBSTRING = "substring"
194
195
  STRPOS = "strpos"
195
196
  CONTAINS = "contains"
197
+ TRIM = "trim"
198
+ REPLACE = "replace"
196
199
 
197
200
  # STRING REGEX
198
201
  REGEXP_CONTAINS = "regexp_contains"
trilogy/core/functions.py CHANGED
@@ -358,6 +358,16 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
358
358
  output_type=DataType.INTEGER,
359
359
  arg_count=2,
360
360
  ),
361
+ FunctionType.REPLACE: FunctionConfig(
362
+ valid_inputs=[
363
+ {DataType.STRING},
364
+ {DataType.STRING},
365
+ {DataType.STRING},
366
+ ],
367
+ output_purpose=Purpose.PROPERTY,
368
+ output_type=DataType.STRING,
369
+ arg_count=3,
370
+ ),
361
371
  FunctionType.CONTAINS: FunctionConfig(
362
372
  valid_inputs=[
363
373
  {DataType.STRING},
@@ -367,6 +377,15 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
367
377
  output_type=DataType.BOOL,
368
378
  arg_count=2,
369
379
  ),
380
+ FunctionType.TRIM: FunctionConfig(
381
+ valid_inputs=[
382
+ {DataType.STRING},
383
+ {DataType.STRING},
384
+ ],
385
+ output_purpose=Purpose.PROPERTY,
386
+ output_type=DataType.BOOL,
387
+ arg_count=2,
388
+ ),
370
389
  FunctionType.SUBSTRING: FunctionConfig(
371
390
  valid_inputs=[{DataType.STRING}, {DataType.INTEGER}, {DataType.INTEGER}],
372
391
  output_purpose=Purpose.PROPERTY,
@@ -688,6 +707,15 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
688
707
  output_type=DataType.INTEGER,
689
708
  arg_count=1,
690
709
  ),
710
+ FunctionType.LOG: FunctionConfig(
711
+ valid_inputs=[
712
+ {DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
713
+ {DataType.INTEGER},
714
+ ],
715
+ output_purpose=Purpose.PROPERTY,
716
+ output_type=DataType.FLOAT,
717
+ arg_count=2,
718
+ ),
691
719
  FunctionType.RANDOM: FunctionConfig(
692
720
  valid_inputs=[],
693
721
  output_purpose=Purpose.PROPERTY,
@@ -775,6 +803,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
775
803
  DataType.FLOAT,
776
804
  DataType.NUMBER,
777
805
  DataType.NUMERIC,
806
+ DataType.BOOL,
778
807
  },
779
808
  output_purpose=Purpose.METRIC,
780
809
  arg_count=1,
@@ -1605,6 +1605,10 @@ def get_concept_arguments(expr) -> List["ConceptRef"]:
1605
1605
  return output
1606
1606
 
1607
1607
 
1608
+ def args_to_pretty(input: set[DataType]) -> str:
1609
+ return ", ".join(sorted([f"'{x.value}'" for x in input if x != DataType.UNKNOWN]))
1610
+
1611
+
1608
1612
  class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1609
1613
  operator: FunctionType
1610
1614
  arg_count: int = Field(default=1)
@@ -1669,9 +1673,10 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1669
1673
  and get_basic_type(arg.datatype.data_type) not in valid_inputs[idx]
1670
1674
  ):
1671
1675
  if arg.datatype != DataType.UNKNOWN:
1676
+
1672
1677
  raise TypeError(
1673
- f"Invalid input datatype {arg.datatype.data_type} passed into position {idx}"
1674
- f" for {operator_name} from concept {arg.name}, valid is {valid_inputs[idx]}"
1678
+ f"Invalid argument type '{arg.datatype.data_type.value}' passed into {operator_name} function in position {idx+1}"
1679
+ f" from concept: {arg.name}. Valid: {args_to_pretty(valid_inputs[idx])}."
1675
1680
  )
1676
1681
  if (
1677
1682
  isinstance(arg, Function)
@@ -1679,8 +1684,8 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1679
1684
  ):
1680
1685
  if arg.output_datatype != DataType.UNKNOWN:
1681
1686
  raise TypeError(
1682
- f"Invalid input datatype {arg.output_datatype} passed into"
1683
- f" {operator_name} from function {arg.operator.name}, need {valid_inputs[idx]}"
1687
+ f"Invalid argument type {arg.output_datatype}' passed into"
1688
+ f" {operator_name} function from function {arg.operator.name} in position {idx+1}. Valid: {args_to_pretty(valid_inputs[idx])}"
1684
1689
  )
1685
1690
  # check constants
1686
1691
  comparisons: List[Tuple[Type, DataType]] = [
@@ -227,7 +227,7 @@ class Environment(BaseModel):
227
227
  alias_origin_lookup: Dict[str, Concept] = Field(default_factory=dict)
228
228
  # TODO: support freezing environments to avoid mutation
229
229
  frozen: bool = False
230
- env_file_path: Path | None = None
230
+ env_file_path: Path | str | None = None
231
231
 
232
232
  def freeze(self):
233
233
  self.frozen = True
@@ -281,8 +281,7 @@ class Environment(BaseModel):
281
281
  )
282
282
  self.add_concept(concept)
283
283
 
284
- def __init__(self, **data):
285
- super().__init__(**data)
284
+ def model_post_init(self, context: Any) -> None:
286
285
  self._add_path_concepts()
287
286
 
288
287
  @classmethod
@@ -97,6 +97,9 @@ def gen_basic_node(
97
97
  and not any(x.address in y.pseudonyms for y in equivalent_optional)
98
98
  and x.address not in ignored_optional
99
99
  ]
100
+ logger.info(
101
+ f"{depth_prefix}{LOGGER_PREFIX} basic node for {concept} has non-equivalent optional {[x.address for x in non_equivalent_optional]}"
102
+ )
100
103
  all_parents: list[BuildConcept] = unique(
101
104
  parent_concepts + non_equivalent_optional, "address"
102
105
  )
@@ -122,10 +125,11 @@ def gen_basic_node(
122
125
  parent_node.add_output_concept(concept)
123
126
  for x in equivalent_optional:
124
127
  parent_node.add_output_concept(x)
125
- targets = [concept] + local_optional
128
+
126
129
  logger.info(
127
130
  f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
128
131
  )
132
+ targets = [concept] + local_optional + equivalent_optional
129
133
  should_hide = [
130
134
  x
131
135
  for x in parent_node.output_concepts
@@ -135,8 +139,14 @@ def gen_basic_node(
135
139
  )
136
140
  ]
137
141
  parent_node.hide_output_concepts(should_hide)
142
+ should_not_hide = [
143
+ x
144
+ for x in parent_node.output_concepts
145
+ if x.address in targets or any(x.address in y.pseudonyms for y in targets)
146
+ ]
147
+ parent_node.unhide_output_concepts(should_not_hide)
138
148
 
139
149
  logger.info(
140
- f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]}"
150
+ f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: output {[x.address for x in parent_node.output_concepts]} hidden {[x for x in parent_node.hidden_concepts]}"
141
151
  )
142
152
  return parent_node
@@ -94,31 +94,45 @@ def determine_induced_minimal_nodes(
94
94
  # inclusion of aggregates can create ambiguous node relation chains
95
95
  # there may be a better way to handle this
96
96
  # can be revisited if we need to connect a derived synonym based on an aggregate
97
- if lookup.derivation in (Derivation.CONSTANT, Derivation.AGGREGATE):
97
+ if lookup.derivation in (
98
+ Derivation.CONSTANT,
99
+ Derivation.AGGREGATE,
100
+ Derivation.FILTER,
101
+ ):
98
102
  nodes_to_remove.append(node)
99
103
  # purge a node if we're already looking for all it's parents
100
104
  if filter_downstream and lookup.derivation not in (Derivation.ROOT,):
101
105
  nodes_to_remove.append(node)
102
-
103
- H.remove_nodes_from(nodes_to_remove)
104
-
105
- H.remove_nodes_from(list(nx.isolates(H)))
106
+ if nodes_to_remove:
107
+ logger.debug(f"Removing nodes {nodes_to_remove} from graph")
108
+ H.remove_nodes_from(nodes_to_remove)
109
+ isolates = list(nx.isolates(H))
110
+ if isolates:
111
+ logger.debug(f"Removing isolates {isolates} from graph")
112
+ H.remove_nodes_from(isolates)
106
113
 
107
114
  zero_out = list(x for x in H.nodes if G.out_degree(x) == 0 and x not in nodelist)
108
115
  while zero_out:
116
+ logger.debug(f"Removing zero out nodes {zero_out} from graph")
109
117
  H.remove_nodes_from(zero_out)
110
118
  zero_out = list(
111
119
  x for x in H.nodes if G.out_degree(x) == 0 and x not in nodelist
112
120
  )
113
-
114
121
  try:
115
122
  paths = nx.multi_source_dijkstra_path(H, nodelist)
116
123
  # logger.debug(f"Paths found for {nodelist}")
117
124
  except nx.exception.NodeNotFound:
118
125
  # logger.debug(f"Unable to find paths for {nodelist}- {str(e)}")
119
126
  return None
120
- H.remove_nodes_from(list(x for x in H.nodes if x not in paths))
127
+ path_removals = list(x for x in H.nodes if x not in paths)
128
+ if path_removals:
129
+ logger.debug(f"Removing paths {path_removals} from graph")
130
+ H.remove_nodes_from(path_removals)
131
+ logger.debug(f"Graph after path removal {H.nodes}")
121
132
  sG: nx.Graph = ax.steinertree.steiner_tree(H, nodelist).copy()
133
+ if not sG.nodes:
134
+ logger.debug(f"No Steiner tree found for nodes {nodelist}")
135
+ return None
122
136
  # logger.debug(f"Steiner tree found for nodes {nodelist} {sG.nodes}")
123
137
  final: nx.DiGraph = nx.subgraph(G, sG.nodes).copy()
124
138
 
@@ -293,7 +307,8 @@ def resolve_weak_components(
293
307
  if not nx.is_weakly_connected(g):
294
308
  break_flag = True
295
309
  continue
296
-
310
+ # from trilogy.hooks.graph_hook import GraphHook
311
+ # GraphHook().query_graph_built(g, highlight_nodes=[concept_to_node(c.with_default_grain()) for c in all_concepts if "__preql_internal" not in c.address])
297
312
  all_graph_concepts = [
298
313
  extract_concept(extract_address(node), environment)
299
314
  for node in g.nodes
trilogy/dialect/base.py CHANGED
@@ -205,6 +205,9 @@ FUNCTION_MAP = {
205
205
  FunctionType.MOD: lambda x: f"({x[0]} % {x[1]})",
206
206
  FunctionType.SQRT: lambda x: f"sqrt({x[0]})",
207
207
  FunctionType.RANDOM: lambda x: "random()",
208
+ FunctionType.LOG: lambda x: (
209
+ f"log({x[0]})" if x[1] == 10 else f"log({x[0]}, {x[1]})"
210
+ ),
208
211
  # aggregate types
209
212
  FunctionType.COUNT_DISTINCT: lambda x: f"count(distinct {x[0]})",
210
213
  FunctionType.COUNT: lambda x: f"count({x[0]})",
@@ -224,6 +227,8 @@ FUNCTION_MAP = {
224
227
  FunctionType.REGEXP_CONTAINS: lambda x: f"REGEXP_CONTAINS({x[0]},{x[1]})",
225
228
  FunctionType.REGEXP_EXTRACT: lambda x: f"REGEXP_EXTRACT({x[0]},{x[1]})",
226
229
  FunctionType.REGEXP_REPLACE: lambda x: f"REGEXP_REPLACE({x[0]},{x[1]}, {x[2]})",
230
+ FunctionType.TRIM: lambda x: f"TRIM({x[0]})",
231
+ FunctionType.REPLACE: lambda x: f"REPLACE({x[0]},{x[1]},{x[2]})",
227
232
  # FunctionType.NOT_LIKE: lambda x: f" CASE WHEN {x[0]} like {x[1]} THEN 0 ELSE 1 END",
228
233
  # date types
229
234
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc({x[0]},{x[1]})",
trilogy/dialect/duckdb.py CHANGED
@@ -33,11 +33,25 @@ def render_sort(args):
33
33
  return f"list_sort({args[0]}, '{order[0]}', '{order[1]}')"
34
34
 
35
35
 
36
+ def render_log(args):
37
+ if len(args) == 1:
38
+ return f"log({args[0]})"
39
+ elif len(args) == 2:
40
+ if int(args[1]) == 10:
41
+ return f"log({args[0]})"
42
+ else:
43
+ # change of base formula
44
+ return f"log({args[0]})/log({args[1]})"
45
+ else:
46
+ raise ValueError("log function requires 1 or 2 arguments")
47
+
48
+
36
49
  FUNCTION_MAP = {
37
50
  FunctionType.COUNT: lambda args: f"count({args[0]})",
38
51
  FunctionType.SUM: lambda args: f"sum({args[0]})",
39
52
  FunctionType.AVG: lambda args: f"avg({args[0]})",
40
53
  FunctionType.LENGTH: lambda args: f"length({args[0]})",
54
+ FunctionType.LOG: lambda args: render_log(args),
41
55
  FunctionType.LIKE: lambda args: (
42
56
  f" CASE WHEN {args[0]} like {args[1]} THEN True ELSE False END"
43
57
  ),
@@ -1793,10 +1793,18 @@ class ParseToObjects(Transformer):
1793
1793
  def fstrpos(self, meta, args):
1794
1794
  return self.function_factory.create_function(args, FunctionType.STRPOS, meta)
1795
1795
 
1796
+ @v_args(meta=True)
1797
+ def freplace(self, meta, args):
1798
+ return self.function_factory.create_function(args, FunctionType.REPLACE, meta)
1799
+
1796
1800
  @v_args(meta=True)
1797
1801
  def fcontains(self, meta, args):
1798
1802
  return self.function_factory.create_function(args, FunctionType.CONTAINS, meta)
1799
1803
 
1804
+ @v_args(meta=True)
1805
+ def ftrim(self, meta, args):
1806
+ return self.function_factory.create_function(args, FunctionType.TRIM, meta)
1807
+
1800
1808
  @v_args(meta=True)
1801
1809
  def fsubstring(self, meta, args):
1802
1810
  return self.function_factory.create_function(args, FunctionType.SUBSTRING, meta)
@@ -1971,6 +1979,12 @@ class ParseToObjects(Transformer):
1971
1979
  args.append(0)
1972
1980
  return self.function_factory.create_function(args, FunctionType.ROUND, meta)
1973
1981
 
1982
+ @v_args(meta=True)
1983
+ def flog(self, meta, args) -> Function:
1984
+ if len(args) == 1:
1985
+ args.append(10)
1986
+ return self.function_factory.create_function(args, FunctionType.LOG, meta)
1987
+
1974
1988
  @v_args(meta=True)
1975
1989
  def ffloor(self, meta, args) -> Function:
1976
1990
  return self.function_factory.create_function(args, FunctionType.FLOOR, meta)
@@ -2074,9 +2088,9 @@ def unpack_visit_error(e: VisitError, text: str | None = None):
2074
2088
  extract = text[e.obj.meta.start_pos - 5 : e.obj.meta.end_pos + 5]
2075
2089
  raise InvalidSyntaxException(
2076
2090
  str(e.orig_exc)
2077
- + " in "
2091
+ + " Raised when parsing rule: "
2078
2092
  + str(e.rule)
2079
- + f" Line: {e.obj.meta.line} ({extract})"
2093
+ + f' Line: {e.obj.meta.line} "...{extract}..."'
2080
2094
  )
2081
2095
  InvalidSyntaxException(
2082
2096
  str(e.orig_exc) + " in " + str(e.rule) + f" Line: {e.obj.meta.line}"
@@ -222,6 +222,8 @@
222
222
  fmul: ("multiply"i "(" expr "," expr ")" )
223
223
  fdiv: ( "divide"i "(" expr "," expr ")")
224
224
  fmod: ( "mod"i "(" expr "," (int_lit | concept_lit ) ")")
225
+ _LOG.1: "log"i "("
226
+ flog: _LOG expr ("," expr)? ")"
225
227
  _ROUND.1: "round"i "("
226
228
  fround: _ROUND expr ("," expr)? ")"
227
229
  _FLOOR.1: "floor"i "("
@@ -234,7 +236,7 @@
234
236
  _RANDOM.1: "random("i
235
237
  frandom: _RANDOM expr ")"
236
238
 
237
- _math_functions: fmul | fdiv | fadd | fsub | fround | ffloor | fceil | fmod | fabs | fsqrt | frandom
239
+ _math_functions: fmul | fdiv | fadd | fsub | fround | ffloor | fceil | fmod | flog | fabs | fsqrt | frandom
238
240
 
239
241
  //generic
240
242
  _fcast_primary: "cast"i "(" expr "as"i data_type ")"
@@ -278,6 +280,10 @@
278
280
  fstrpos: _STRPOS expr "," expr ")"
279
281
  _CONTAINS.1: "contains("i
280
282
  fcontains: _CONTAINS expr "," expr ")"
283
+ _TRIM.1: "trim("i
284
+ ftrim: _TRIM expr ")"
285
+ _REPLACE.1: "replace("i
286
+ freplace: _REPLACE expr "," expr "," expr ")"
281
287
  _SUBSTRING.1: "substring("i
282
288
  fsubstring: _SUBSTRING expr "," expr "," expr ")"
283
289
  _REGEXP_EXTRACT.1: "regexp_extract("
@@ -287,7 +293,7 @@
287
293
  _REGEXP_REPLACE.1: "regexp_replace("
288
294
  fregexp_replace: _REGEXP_REPLACE expr "," expr "," expr ")"
289
295
 
290
- _string_functions: like | ilike | upper | flower | fsplit | fstrpos | fsubstring | fcontains | fregexp_extract | fregexp_contains | fregexp_replace
296
+ _string_functions: like | ilike | upper | flower | fsplit | fstrpos | fsubstring | fcontains | ftrim | freplace | fregexp_extract | fregexp_contains | fregexp_replace
291
297
 
292
298
  //array_functions
293
299
  _ARRAY_SUM.1: "array_sum("i