pytrilogy 0.0.3.42__py3-none-any.whl → 0.0.3.44__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.42
3
+ Version: 0.0.3.44
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.42.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=6Oo1hj7rRiJ3WTlh1NH1PAfbHWNk1Zgr5f5DpIefPy8,303
1
+ pytrilogy-0.0.3.44.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=_DLr4-giprU_EZXs7LGyoeoIt8LaSs4wYqbdAajaVr4,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  trilogy/constants.py,sha256=5eQxk1A0pv-TQk3CCvgZCFA9_K-6nxrOm7E5Lxd7KIY,1652
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
@@ -22,8 +22,8 @@ trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
22
22
  trilogy/core/optimization.py,sha256=aihzx4-2-mSjx5td1TDTYGvc7e9Zvy-_xEyhPqLS-Ig,8314
23
23
  trilogy/core/query_processor.py,sha256=Vl-u0F0rbqI2liv82yJgiZCB255Kx_KiuzZVHL6aeTM,19459
24
24
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- trilogy/core/models/author.py,sha256=hS1caD8y7XWRBlHfwgZOrBcz3TisDPba8joFaiEXxX0,77072
26
- trilogy/core/models/build.py,sha256=NdDRcgvNoa2CHoXcPvWAUkCA2TRUKYm0zIl5NBx0sfo,61796
25
+ trilogy/core/models/author.py,sha256=7lPUVm1uCXYsyV85p34AvtLLzHqDGCeesTUT8ZHCwo4,76859
26
+ trilogy/core/models/build.py,sha256=EsI7BLmFXdxj1an3NnKR_Qm79tcjlFKjmLjmt3_v2eA,61829
27
27
  trilogy/core/models/build_environment.py,sha256=s_C9xAHuD3yZ26T15pWVBvoqvlp2LdZ8yjsv2_HdXLk,5363
28
28
  trilogy/core/models/core.py,sha256=wx6hJcFECMG-Ij972ADNkr-3nFXkYESr82ObPiC46_U,10875
29
29
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
@@ -40,7 +40,7 @@ trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuX
40
40
  trilogy/core/processing/utility.py,sha256=rfzdgl-vWkCyhLzXNNuWgPLK59eiYypQb6TdZKymUqk,21469
41
41
  trilogy/core/processing/node_generators/__init__.py,sha256=o8rOFHPSo-s_59hREwXMW6gjUJCsiXumdbJNozHUf-Y,800
42
42
  trilogy/core/processing/node_generators/basic_node.py,sha256=UVsXMn6jTjm_ofVFt218jAS11s4RV4zD781vP4im-GI,3371
43
- trilogy/core/processing/node_generators/common.py,sha256=ZsDzThjm_mAtdQpKAg8QIJiPVZ4KuUkKyilj4eOhSDs,9439
43
+ trilogy/core/processing/node_generators/common.py,sha256=nVeH_AdO58ygtNSO0wNgMR7_h2D0dFSGM_rh1fJd4Yc,9468
44
44
  trilogy/core/processing/node_generators/filter_node.py,sha256=lT167yBgy3P9sDBM1Cjj0PKSXro8dvGtBmc8nwsUjig,8366
45
45
  trilogy/core/processing/node_generators/group_node.py,sha256=kO-ersxIL04rZwX5-vFIFQQnp357PFo_7ZKXoGq3wyc,5989
46
46
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
@@ -52,7 +52,7 @@ trilogy/core/processing/node_generators/select_node.py,sha256=Y-zO0AFkTrpi2Lyebj
52
52
  trilogy/core/processing/node_generators/synonym_node.py,sha256=9LHK2XHDjbyTLjmDQieskG8fqbiSpRnFOkfrutDnOTE,2258
53
53
  trilogy/core/processing/node_generators/union_node.py,sha256=VNo6Oey4p8etU9xrOh2oTT2lIOTvY6PULUPRvVa2uxU,2877
54
54
  trilogy/core/processing/node_generators/unnest_node.py,sha256=cOEKnMRzXUW3bwmiOlgn3E1-B38osng0dh2pDykwITY,2410
55
- trilogy/core/processing/node_generators/window_node.py,sha256=MjLmFKUiS-_p-Ak_9mr3becGde9eu5frxmqI7plIETY,5808
55
+ trilogy/core/processing/node_generators/window_node.py,sha256=RUHgpYovQObFod1xRIMWtDzMcxwlm4-1Fdrf_Cuw5W4,6346
56
56
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=GMW07bb6hXurhF0hZLYoMAKSIS65tat5hwBjvqqPeSA,6516
58
58
  trilogy/core/processing/nodes/__init__.py,sha256=Lxr3rs_bqOAtMtn3DHIkY058ZzjyLM5mSfGMKW2z0NY,5555
@@ -71,7 +71,7 @@ trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWh
71
71
  trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
72
72
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  trilogy/dialect/base.py,sha256=vp6_9fUkblAWVpCXGBIcoAx6N7vof9M7s9t6-b_waUY,41409
74
- trilogy/dialect/bigquery.py,sha256=j5PQvwMUMcLHaxZgbqe6P-v-pwhHDQ38z8uK6ecxzR0,3359
74
+ trilogy/dialect/bigquery.py,sha256=7LcgPLDkeNBk6YTfaE-RBBi7SjWFV-jjuvZM1VMIXqk,3350
75
75
  trilogy/dialect/common.py,sha256=XjHkP8Dqezjkd2JU5xoAlMRS_6HNyXQCF4CykLK3C8o,5011
76
76
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
77
77
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
@@ -87,13 +87,13 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
87
87
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
88
88
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
- trilogy/parsing/common.py,sha256=2KwR86ZNH04tcs500l02jLPkFaStEYc9wtR1NVO6cYo,26411
90
+ trilogy/parsing/common.py,sha256=U9RNi1GyPTQaitZGwXy1QftdC5PWYArP7V8t-v3H8Po,27157
91
91
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
92
92
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
93
93
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
94
- trilogy/parsing/parse_engine.py,sha256=GTraw3-Dbdo36yhprizifZY-wbm_ub8ktI1nMEziee0,64723
94
+ trilogy/parsing/parse_engine.py,sha256=9SO2q8m5MlZo_Eho-_r6hmTSm5VH38k47C2iHTtYwjU,68224
95
95
  trilogy/parsing/render.py,sha256=hI4y-xjXrEXvHslY2l2TQ8ic0zAOpN41ADH37J2_FZY,19047
96
- trilogy/parsing/trilogy.lark,sha256=2Noe58vGYteilKd6w-np3fb4lzWI-G9Gt0AMyOMVw3k,13735
96
+ trilogy/parsing/trilogy.lark,sha256=zbDAIG7gpsImxBtteD8E2pKwcJCGpM-rEQDRqpgzoSQ,13717
97
97
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
99
99
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -102,8 +102,8 @@ trilogy/std/display.preql,sha256=2BbhvqR4rcltyAbOXAUo7SZ_yGFYZgFnurglHMbjW2g,40
102
102
  trilogy/std/geography.preql,sha256=-fqAGnBL6tR-UtT8DbSek3iMFg66ECR_B_41pODxv-k,504
103
103
  trilogy/std/money.preql,sha256=ZHW-csTX-kYbOLmKSO-TcGGgQ-_DMrUXy0BjfuJSFxM,80
104
104
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
105
- pytrilogy-0.0.3.42.dist-info/METADATA,sha256=L_128KNMAQLKLe33FsvUWvD5NRHCKqzkvmHT1Xx6o4c,9100
106
- pytrilogy-0.0.3.42.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
107
- pytrilogy-0.0.3.42.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
- pytrilogy-0.0.3.42.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
- pytrilogy-0.0.3.42.dist-info/RECORD,,
105
+ pytrilogy-0.0.3.44.dist-info/METADATA,sha256=-TUyQi3fENhsnYhqsy2HiD-thLyAz6S8TAmgdVuaCZo,9100
106
+ pytrilogy-0.0.3.44.dist-info/WHEEL,sha256=GHB6lJx2juba1wDgXDNlMTyM13ckjBMKf-OnwgKOCtA,91
107
+ pytrilogy-0.0.3.44.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
108
+ pytrilogy-0.0.3.44.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
109
+ pytrilogy-0.0.3.44.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.0)
2
+ Generator: setuptools (80.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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.42"
7
+ __version__ = "0.0.3.44"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -629,18 +629,8 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
629
629
  )
630
630
  elif self.operator in (ComparisonOperator.IN, ComparisonOperator.NOT_IN):
631
631
  right_type = arg_to_datatype(self.right)
632
- if not any(
633
- [
634
- isinstance(self.right, ConceptRef),
635
- right_type in (DataType.LIST,),
636
- isinstance(right_type, (ListType, ListWrapper, TupleWrapper)),
637
- ]
638
- ):
639
- raise SyntaxError(
640
- f"Cannot use {self.operator.value} with non-list, non-tuple, non-concept object {self.right} in {str(self)}"
641
- )
642
632
 
643
- elif isinstance(right_type, ListType) and not is_compatible_datatype(
633
+ if isinstance(right_type, ListType) and not is_compatible_datatype(
644
634
  arg_to_datatype(self.left), right_type.value_data_type
645
635
  ):
646
636
  raise SyntaxError(
@@ -1916,7 +1906,7 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
1916
1906
 
1917
1907
 
1918
1908
  class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
1919
- content: ConceptRef
1909
+ content: Expr
1920
1910
  where: "WhereClause"
1921
1911
 
1922
1912
  @field_validator("content", mode="before")
@@ -1932,13 +1922,21 @@ class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
1932
1922
  self, source: Concept, target: Concept, modifiers: List[Modifier]
1933
1923
  ) -> "FilterItem":
1934
1924
  return FilterItem.model_construct(
1935
- content=self.content.with_merge(source, target, modifiers),
1925
+ content=(
1926
+ self.content.with_merge(source, target, modifiers)
1927
+ if isinstance(self.content, Mergeable)
1928
+ else self.content
1929
+ ),
1936
1930
  where=self.where.with_merge(source, target, modifiers),
1937
1931
  )
1938
1932
 
1939
1933
  def with_namespace(self, namespace: str) -> "FilterItem":
1940
1934
  return FilterItem.model_construct(
1941
- content=self.content.with_namespace(namespace),
1935
+ content=(
1936
+ self.content.with_namespace(namespace)
1937
+ if isinstance(self.content, Namespaced)
1938
+ else self.content
1939
+ ),
1942
1940
  where=self.where.with_namespace(namespace),
1943
1941
  )
1944
1942
 
@@ -1496,22 +1496,43 @@ class Factory:
1496
1496
  ):
1497
1497
  return base
1498
1498
 
1499
+ def instantiate_concept(
1500
+ self,
1501
+ arg: (
1502
+ AggregateWrapper
1503
+ | FunctionCallWrapper
1504
+ | WindowItem
1505
+ | FilterItem
1506
+ | Function
1507
+ | ListWrapper[Any]
1508
+ | MapWrapper[Any, Any]
1509
+ | int
1510
+ | float
1511
+ | str
1512
+ ),
1513
+ ) -> tuple[Concept, BuildConcept]:
1514
+ from trilogy.parsing.common import arbitrary_to_concept
1515
+
1516
+ new = arbitrary_to_concept(
1517
+ arg,
1518
+ environment=self.environment,
1519
+ )
1520
+ built = self.build(new)
1521
+ self.local_concepts[new.address] = built
1522
+ return new, built
1523
+
1499
1524
  @build.register
1500
1525
  def _(self, base: None) -> None:
1501
1526
  return base
1502
1527
 
1503
1528
  @build.register
1504
1529
  def _(self, base: Function) -> BuildFunction | BuildAggregateWrapper:
1505
- from trilogy.parsing.common import arbitrary_to_concept
1506
1530
 
1507
1531
  raw_args: list[Concept | FuncArgs] = []
1508
1532
  for arg in base.arguments:
1509
1533
  # to do proper discovery, we need to inject virtual intermediate ocncepts
1510
1534
  if isinstance(arg, (AggregateWrapper, FilterItem, WindowItem)):
1511
- narg = arbitrary_to_concept(
1512
- arg,
1513
- environment=self.environment,
1514
- )
1535
+ narg, _ = self.instantiate_concept(arg)
1515
1536
  raw_args.append(narg)
1516
1537
  else:
1517
1538
  raw_args.append(arg)
@@ -1532,24 +1553,16 @@ class Factory:
1532
1553
  for x in arguments:
1533
1554
  if isinstance(x, (ConceptRef, Concept)):
1534
1555
  final_args.append(x)
1535
- elif isinstance(x, (AggregateWrapper, FilterItem, WindowItem)):
1536
- newx = arbitrary_to_concept(
1537
- x,
1538
- environment=self.environment,
1539
- )
1540
- final_args.append(newx)
1541
1556
  else:
1542
1557
  # constants, etc, can be ignored for group
1543
1558
  continue
1544
- group_base = arbitrary_to_concept(
1559
+ _, rval = self.instantiate_concept(
1545
1560
  AggregateWrapper(
1546
1561
  function=group_base.lineage.function,
1547
1562
  by=final_args,
1548
- ),
1549
- environment=self.environment,
1563
+ )
1550
1564
  )
1551
1565
 
1552
- rval = self.build(group_base)
1553
1566
  return BuildFunction.model_construct(
1554
1567
  operator=base.operator,
1555
1568
  arguments=[rval, *[self.build(c) for c in raw_args[1:]]],
@@ -1580,20 +1593,13 @@ class Factory:
1580
1593
 
1581
1594
  @build.register
1582
1595
  def _(self, base: CaseWhen) -> BuildCaseWhen:
1583
- from trilogy.parsing.common import arbitrary_to_concept
1584
1596
 
1585
1597
  comparison = base.comparison
1586
1598
  if isinstance(comparison, (AggregateWrapper, FilterItem, WindowItem)):
1587
- comparison = arbitrary_to_concept(
1588
- comparison,
1589
- environment=self.environment,
1590
- )
1599
+ comparison, _ = self.instantiate_concept(comparison)
1591
1600
  expr: Concept | FuncArgs = base.expr
1592
1601
  if isinstance(expr, (AggregateWrapper, FilterItem, WindowItem)):
1593
- expr = arbitrary_to_concept(
1594
- expr,
1595
- environment=self.environment,
1596
- )
1602
+ expr, _ = self.instantiate_concept(expr)
1597
1603
  return BuildCaseWhen.model_construct(
1598
1604
  comparison=self.build(comparison),
1599
1605
  expr=self.build(expr),
@@ -1615,6 +1621,7 @@ class Factory:
1615
1621
  else:
1616
1622
  build_lineage = None
1617
1623
  derivation = Concept.calculate_derivation(build_lineage, base.purpose)
1624
+
1618
1625
  granularity = Concept.calculate_granularity(
1619
1626
  derivation, final_grain, build_lineage
1620
1627
  )
@@ -1675,16 +1682,12 @@ class Factory:
1675
1682
 
1676
1683
  @build.register
1677
1684
  def _(self, base: OrderItem) -> BuildOrderItem:
1678
- from trilogy.parsing.common import arbitrary_to_concept
1679
1685
 
1680
1686
  bexpr: Any
1681
1687
  if isinstance(base.expr, (AggregateWrapper, WindowItem, FilterItem)) or (
1682
1688
  isinstance(base.expr, Function) and base.expr.operator == FunctionType.GROUP
1683
1689
  ):
1684
- bexpr = arbitrary_to_concept(
1685
- base.expr,
1686
- environment=self.environment,
1687
- )
1690
+ bexpr, _ = self.instantiate_concept(base.expr)
1688
1691
  else:
1689
1692
  bexpr = base.expr
1690
1693
  return BuildOrderItem.model_construct(
@@ -1707,15 +1710,10 @@ class Factory:
1707
1710
 
1708
1711
  @build.register
1709
1712
  def _(self, base: WindowItem) -> BuildWindowItem:
1710
- # to do proper discovery, we need to inject virtual intermediate ocncepts
1711
- from trilogy.parsing.common import arbitrary_to_concept
1712
1713
 
1713
1714
  content: Concept | FuncArgs = base.content
1714
1715
  if isinstance(content, (AggregateWrapper, FilterItem, WindowItem)):
1715
- content = arbitrary_to_concept(
1716
- content,
1717
- environment=self.environment,
1718
- )
1716
+ content, _ = self.instantiate_concept(content)
1719
1717
  final_by = []
1720
1718
  for x in base.order_by:
1721
1719
  if (
@@ -1743,30 +1741,26 @@ class Factory:
1743
1741
 
1744
1742
  @build.register
1745
1743
  def _(self, base: SubselectComparison) -> BuildSubselectComparison:
1744
+ right: Any = base.right
1745
+ if isinstance(base.right, (AggregateWrapper, WindowItem, FilterItem, Function)):
1746
+ right_c, _ = self.instantiate_concept(base.right)
1747
+ right = right_c
1746
1748
  return BuildSubselectComparison.model_construct(
1747
- left=(self.build(base.left)),
1748
- right=(self.build(base.right)),
1749
+ left=self.build(base.left),
1750
+ right=self.build(right),
1749
1751
  operator=base.operator,
1750
1752
  )
1751
1753
 
1752
1754
  @build.register
1753
1755
  def _(self, base: Comparison) -> BuildComparison:
1754
- from trilogy.parsing.common import arbitrary_to_concept
1755
1756
 
1756
1757
  left = base.left
1757
1758
  if isinstance(left, (AggregateWrapper, WindowItem, FilterItem)):
1758
- left_c = arbitrary_to_concept(
1759
- left,
1760
- environment=self.environment,
1761
- )
1759
+ left_c, _ = self.instantiate_concept(left)
1762
1760
  left = left_c # type: ignore
1763
1761
  right = base.right
1764
1762
  if isinstance(right, (AggregateWrapper, WindowItem, FilterItem)):
1765
- right_c = arbitrary_to_concept(
1766
- right,
1767
- environment=self.environment,
1768
- )
1769
-
1763
+ right_c, _ = self.instantiate_concept(right)
1770
1764
  right = right_c # type: ignore
1771
1765
  return BuildComparison.model_construct(
1772
1766
  left=(self.build(left)),
@@ -1826,6 +1820,13 @@ class Factory:
1826
1820
 
1827
1821
  @build.register
1828
1822
  def _(self, base: FilterItem) -> BuildFilterItem:
1823
+ if isinstance(
1824
+ base.content, (Function, AggregateWrapper, WindowItem, FilterItem)
1825
+ ):
1826
+ _, built = self.instantiate_concept(base.content)
1827
+ return BuildFilterItem.model_construct(
1828
+ content=built, where=self.build(base.where)
1829
+ )
1829
1830
  return BuildFilterItem.model_construct(
1830
1831
  content=self.build(base.content), where=self.build(base.where)
1831
1832
  )
@@ -1969,6 +1970,10 @@ class Factory:
1969
1970
  new.datasources[k] = self.build(d)
1970
1971
  for k, a in base.alias_origin_lookup.items():
1971
1972
  new.alias_origin_lookup[k] = self.build(a)
1973
+ # add in anything that was built as a side-effect
1974
+ for bk, bv in self.local_concepts.items():
1975
+ if bk not in new.concepts:
1976
+ new.concepts[bk] = bv
1972
1977
  new.gen_concept_list_caches()
1973
1978
  return new
1974
1979
 
@@ -178,7 +178,7 @@ def gen_enrichment_node(
178
178
  for x in extra_required
179
179
  ):
180
180
  log_lambda(
181
- f"{str(type(base_node).__name__)} returning property optimized enrichment node"
181
+ f"{str(type(base_node).__name__)} returning property optimized enrichment node for {extra_required[0].keys}"
182
182
  )
183
183
  return gen_property_enrichment_node(
184
184
  base_node,
@@ -32,7 +32,9 @@ def resolve_window_parent_concepts(
32
32
  if concept.lineage.order_by:
33
33
  for item in concept.lineage.order_by:
34
34
  base += item.concept_arguments
35
-
35
+ if concept.grain:
36
+ for gitem in concept.grain.components:
37
+ base.append(environment.concepts[gitem])
36
38
  return concept.lineage.content, unique(base, "address")
37
39
 
38
40
 
@@ -61,6 +63,9 @@ def gen_window_node(
61
63
  # append in keys to get the right grain
62
64
  if concept.keys:
63
65
  for item in concept.keys:
66
+ logger.info(
67
+ f"{padding(depth)}{LOGGER_PREFIX} appending search for key {item}"
68
+ )
64
69
  targets.append(environment.concepts[item])
65
70
  additional_outputs = []
66
71
  if equivalent_optional:
@@ -72,10 +77,15 @@ def gen_window_node(
72
77
  additional_outputs.append(x)
73
78
 
74
79
  grain_equivalents = [
75
- x for x in local_optional if x.keys and all([key in targets for key in x.keys])
80
+ x
81
+ for x in local_optional
82
+ if x.keys
83
+ and all([key in targets for key in x.keys])
84
+ and x.grain == concept.grain
76
85
  ]
77
86
 
78
87
  for x in grain_equivalents:
88
+ logger.info("Appending grain equivalent %s", x)
79
89
  targets.append(x)
80
90
 
81
91
  # finally, the ones we'll need to enrich
@@ -123,20 +133,24 @@ def gen_window_node(
123
133
  )
124
134
  _window_node.rebuild_cache()
125
135
  _window_node.resolve()
136
+
126
137
  window_node = StrategyNode(
127
138
  input_concepts=[concept] + additional_outputs + parent_concepts + targets,
128
139
  output_concepts=[concept] + additional_outputs + parent_concepts + targets,
129
140
  environment=environment,
130
141
  parents=[_window_node],
131
142
  preexisting_conditions=conditions.conditional if conditions else None,
132
- # hidden_concepts=[
133
- # x.address for x in parent_concepts if x.address not in local_optional
134
- # ],
143
+ grain=BuildGrain.from_concepts(
144
+ concepts=[concept] + additional_outputs + parent_concepts + targets,
145
+ environment=environment,
146
+ ),
135
147
  )
136
148
  if not non_equivalent_optional:
137
149
  logger.info(
138
150
  f"{padding(depth)}{LOGGER_PREFIX} no optional concepts, returning window node"
139
151
  )
152
+ # prune outputs if we don't need join keys
153
+ window_node.set_output_concepts([concept] + additional_outputs + targets)
140
154
  return window_node
141
155
 
142
156
  missing_optional = [
@@ -34,7 +34,7 @@ FUNCTION_MAP = {
34
34
 
35
35
  FUNCTION_GRAIN_MATCH_MAP = {
36
36
  **FUNCTION_MAP,
37
- FunctionType.COUNT: lambda args: f"{args[0]}",
37
+ FunctionType.COUNT: lambda args: "1",
38
38
  FunctionType.SUM: lambda args: f"{args[0]}",
39
39
  FunctionType.AVG: lambda args: f"{args[0]}",
40
40
  }
trilogy/parsing/common.py CHANGED
@@ -458,7 +458,25 @@ def filter_item_to_concept(
458
458
  metadata: Metadata | None = None,
459
459
  ) -> Concept:
460
460
  fmetadata = metadata or Metadata()
461
- cparent = environment.concepts[parent.content.address]
461
+ if isinstance(parent.content, ConceptRef):
462
+ cparent = environment.concepts[parent.content.address]
463
+ elif isinstance(
464
+ parent.content,
465
+ (
466
+ FilterItem,
467
+ AggregateWrapper,
468
+ FunctionCallWrapper,
469
+ WindowItem,
470
+ Function,
471
+ ListWrapper,
472
+ MapWrapper,
473
+ ),
474
+ ):
475
+ cparent = arbitrary_to_concept(parent.content, environment, namespace=namespace)
476
+ else:
477
+ raise NotImplementedError(
478
+ f"Filter item with non ref content {parent.content} not yet supported"
479
+ )
462
480
  modifiers = get_upstream_modifiers(
463
481
  cparent.concept_arguments, environment=environment
464
482
  )
@@ -494,24 +512,6 @@ def window_item_to_concept(
494
512
  metadata: Metadata | None = None,
495
513
  ) -> Concept:
496
514
  fmetadata = metadata or Metadata()
497
- # if isinstance(
498
- # parent.content,
499
- # (
500
- # AggregateWrapper
501
- # | FunctionCallWrapper
502
- # | WindowItem
503
- # | FilterItem
504
- # | Function
505
- # | ListWrapper
506
- # | MapWrapper
507
- # ),
508
- # ):
509
- # new_parent = arbitrary_to_concept(
510
- # parent.content, environment=environment, namespace=namespace
511
- # )
512
- # environment.add_concept(new_parent)
513
- # parent = parent.model_copy(update={"content": new_parent.reference})
514
-
515
515
  if not isinstance(parent.content, ConceptRef):
516
516
  raise NotImplementedError(
517
517
  f"Window function wiht non ref content {parent.content} not yet supported"
@@ -523,16 +523,26 @@ def window_item_to_concept(
523
523
  local_purpose, keys = get_purpose_and_keys(None, (bcontent,), environment)
524
524
  else:
525
525
  local_purpose = Purpose.PROPERTY
526
- keys = {
527
- bcontent.address,
528
- }
526
+ keys = Grain.from_concepts(
527
+ [bcontent.address] + [y.address for y in parent.over], environment
528
+ ).components
529
529
 
530
+ # when including the order by in discovery grain
530
531
  if parent.order_by:
531
532
  grain_components = parent.over + [bcontent.output]
532
533
  for item in parent.order_by:
533
- grain_components += item.concept_arguments
534
+ # confirm that it's not just an aggregate at the grain of the stuff we're already keying of of
535
+ # in which case we can ignore contributions
536
+ if (
537
+ isinstance(item.expr, AggregateWrapper)
538
+ and set([x.address for x in item.expr.by]) == keys
539
+ ):
540
+ continue
541
+ else:
542
+ grain_components += item.concept_arguments
534
543
  else:
535
544
  grain_components = parent.over + [bcontent.output]
545
+
536
546
  final_grain = Grain.from_concepts(grain_components, environment)
537
547
  modifiers = get_upstream_modifiers(bcontent.concept_arguments, environment)
538
548
  datatype = parent.content.datatype
@@ -651,7 +661,9 @@ def rowset_concept(
651
661
  orig_concept = environment.concepts[orig_address.address]
652
662
  name = orig_concept.name
653
663
  if isinstance(orig_concept.lineage, FilterItem):
654
- if orig_concept.lineage.where == rowset.select.where_clause:
664
+ if orig_concept.lineage.where == rowset.select.where_clause and isinstance(
665
+ orig_concept.lineage.content, (ConceptRef, Concept)
666
+ ):
655
667
  name = environment.concepts[orig_concept.lineage.content.address].name
656
668
  base_namespace = (
657
669
  f"{rowset.name}.{orig_concept.namespace}"
@@ -761,7 +773,10 @@ def arbitrary_to_concept(
761
773
  )
762
774
  elif isinstance(parent, FilterItem):
763
775
  if not name:
764
- name = f"{VIRTUAL_CONCEPT_PREFIX}_filter_{parent.content.name}_{string_to_hash(str(parent))}"
776
+ if isinstance(parent.content, ConceptRef):
777
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_filter_{parent.content.name}_{string_to_hash(str(parent))}"
778
+ else:
779
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_filter_{string_to_hash(str(parent))}"
765
780
  return filter_item_to_concept(
766
781
  parent,
767
782
  name,
@@ -495,7 +495,7 @@ class ParseToObjects(Transformer):
495
495
  return ComparisonOperator([x.value.lower() for x in args])
496
496
 
497
497
  def COMPARISON_OPERATOR(self, args) -> ComparisonOperator:
498
- return ComparisonOperator(args)
498
+ return ComparisonOperator(args.strip())
499
499
 
500
500
  def LOGICAL_OPERATOR(self, args) -> BooleanOperator:
501
501
  return BooleanOperator(args.lower())
@@ -682,8 +682,7 @@ class ParseToObjects(Transformer):
682
682
  return ConceptDerivationStatement(concept=concept)
683
683
 
684
684
  raise SyntaxError(
685
- f"Received invalid type {type(args[2])} {args[2]} as input to select"
686
- " transform"
685
+ f"Received invalid type {type(args[2])} {args[2]} as input to concept derivation: `{self.text_lookup[self.token_address][meta.start_pos:meta.end_pos]}`"
687
686
  )
688
687
 
689
688
  @v_args(meta=True)
@@ -1255,7 +1254,7 @@ class ParseToObjects(Transformer):
1255
1254
  intersection = base.locally_derived.intersection(pre_keys)
1256
1255
  if intersection:
1257
1256
  raise ParseError(
1258
- f"Select statement {base} has derived concepts {list(intersection)} that shadow existing environment concepts, which may cause unexpected behavior. Rename these."
1257
+ f"Select statement {base} creates new derived concepts {list(intersection)} from transformations with identical name(s) to existing concept(s). Do you mean to drop the calculation and directly use the existing concept? If not, alias these concept(s) under new names."
1259
1258
  )
1260
1259
  return base
1261
1260
 
@@ -1365,11 +1364,78 @@ class ParseToObjects(Transformer):
1365
1364
  def literal(self, args):
1366
1365
  return args[0]
1367
1366
 
1367
+ def product_operator(self, args) -> Function | Any:
1368
+ if len(args) == 1:
1369
+ return args[0]
1370
+ result = args[0]
1371
+ for i in range(1, len(args), 2):
1372
+ new_result = None
1373
+ op = args[i]
1374
+ right = args[i + 1]
1375
+ if op == "*":
1376
+ new_result = self.function_factory.create_function(
1377
+ [result, right], operator=FunctionType.MULTIPLY
1378
+ )
1379
+ elif op == "/":
1380
+ new_result = self.function_factory.create_function(
1381
+ [result, right], operator=FunctionType.DIVIDE
1382
+ )
1383
+ elif op == "%":
1384
+ new_result = self.function_factory.create_function(
1385
+ [result, right], operator=FunctionType.MOD
1386
+ )
1387
+ else:
1388
+ raise ValueError(f"Unknown operator: {op}")
1389
+ result = new_result
1390
+ return new_result
1391
+
1392
+ def PLUS_OR_MINUS(self, args) -> str:
1393
+ return args.value
1394
+
1395
+ def MULTIPLY_DIVIDE_PERCENT(self, args) -> str:
1396
+ return args[0]
1397
+
1398
+ @v_args(meta=True)
1399
+ def sum_operator(self, meta: Meta, args) -> Function | Any:
1400
+ if len(args) == 1:
1401
+ return args[0]
1402
+ result = args[0]
1403
+ for i in range(1, len(args), 2):
1404
+ new_result = None
1405
+ op = args[i]
1406
+ right = args[i + 1]
1407
+ if op == "+":
1408
+ new_result = self.function_factory.create_function(
1409
+ [result, right], operator=FunctionType.ADD, meta=meta
1410
+ )
1411
+ elif op == "-":
1412
+ new_result = self.function_factory.create_function(
1413
+ [result, right], operator=FunctionType.SUBTRACT, meta=meta
1414
+ )
1415
+ elif op == "||":
1416
+ new_result = self.function_factory.create_function(
1417
+ [result, right], operator=FunctionType.CONCAT, meta=meta
1418
+ )
1419
+ elif op == "like":
1420
+ new_result = self.function_factory.create_function(
1421
+ [result, right], operator=FunctionType.LIKE, meta=meta
1422
+ )
1423
+ else:
1424
+ raise ValueError(f"Unknown operator: {op}")
1425
+ result = new_result
1426
+ return result
1427
+
1368
1428
  def comparison(self, args) -> Comparison:
1369
- if args[1] == ComparisonOperator.IN:
1370
- raise SyntaxError
1429
+ if len(args) == 1:
1430
+ return args[0]
1371
1431
  left = args[0]
1372
1432
  right = args[2]
1433
+ if args[1] in (ComparisonOperator.IN, ComparisonOperator.NOT_IN):
1434
+ return SubselectComparison(
1435
+ left=left,
1436
+ right=right,
1437
+ operator=args[1],
1438
+ )
1373
1439
  return Comparison(left=left, right=right, operator=args[1])
1374
1440
 
1375
1441
  def between_comparison(self, args) -> Conditional:
@@ -1464,7 +1530,7 @@ class ParseToObjects(Transformer):
1464
1530
  return DatePart(args.value)
1465
1531
 
1466
1532
  @v_args(meta=True)
1467
- def window_item(self, meta, args) -> WindowItem:
1533
+ def window_item(self, meta: Meta, args) -> WindowItem:
1468
1534
  type: WindowType = args[0]
1469
1535
  order_by = []
1470
1536
  over = []
@@ -1486,7 +1552,10 @@ class ParseToObjects(Transformer):
1486
1552
  else:
1487
1553
  concept = arbitrary_to_concept(item, environment=self.environment)
1488
1554
  self.environment.add_concept(concept, meta=meta)
1489
- assert concept
1555
+ if not concept:
1556
+ raise ParseError(
1557
+ f"Window statements must be on fields, not constants - error in: `{self.text_lookup[self.parse_address][meta.start_pos:meta.end_pos]}`"
1558
+ )
1490
1559
  return WindowItem(
1491
1560
  type=type,
1492
1561
  content=concept.reference,
@@ -1497,13 +1566,14 @@ class ParseToObjects(Transformer):
1497
1566
 
1498
1567
  def filter_item(self, args) -> FilterItem:
1499
1568
  where: WhereClause
1500
- string_concept, raw = args
1569
+ expr, raw = args
1501
1570
  if isinstance(raw, WhereClause):
1502
1571
  where = raw
1503
1572
  else:
1504
1573
  where = WhereClause(conditional=raw)
1505
- concept = self.environment.concepts[string_concept].reference
1506
- return FilterItem(content=concept, where=where)
1574
+ if isinstance(expr, str):
1575
+ expr = self.environment.concepts[expr].reference
1576
+ return FilterItem(content=expr, where=where)
1507
1577
 
1508
1578
  # BEGIN FUNCTIONS
1509
1579
  @v_args(meta=True)
@@ -1722,10 +1792,7 @@ class ParseToObjects(Transformer):
1722
1792
  def fyear(self, meta, args):
1723
1793
  return self.function_factory.create_function(args, FunctionType.YEAR, meta)
1724
1794
 
1725
- # utility functions
1726
- @v_args(meta=True)
1727
- def fcast(self, meta, args) -> Function:
1728
- # if it's casting a constant, we'll process that directly
1795
+ def internal_fcast(self, meta, args):
1729
1796
  args = process_function_args(args, meta=meta, environment=self.environment)
1730
1797
  if isinstance(args[0], str):
1731
1798
  processed: date | datetime | int | float | bool | str
@@ -1750,9 +1817,15 @@ class ParseToObjects(Transformer):
1750
1817
  )
1751
1818
  return self.function_factory.create_function(args, FunctionType.CAST, meta)
1752
1819
 
1820
+ # utility functions
1821
+ @v_args(meta=True)
1822
+ def fcast(self, meta, args) -> Function:
1823
+ return self.internal_fcast(meta, args)
1824
+
1753
1825
  # math functions
1754
1826
  @v_args(meta=True)
1755
1827
  def fadd(self, meta, args) -> Function:
1828
+
1756
1829
  return self.function_factory.create_function(args, FunctionType.ADD, meta)
1757
1830
 
1758
1831
  @v_args(meta=True)
@@ -1815,16 +1888,24 @@ class ParseToObjects(Transformer):
1815
1888
  return self.function_factory.create_function(args, FunctionType.BOOL, meta)
1816
1889
 
1817
1890
 
1818
- def unpack_visit_error(e: VisitError):
1891
+ def unpack_visit_error(e: VisitError, text: str | None = None):
1819
1892
  """This is required to get exceptions from imports, which would
1820
1893
  raise nested VisitErrors"""
1821
1894
  if isinstance(e.orig_exc, VisitError):
1822
- unpack_visit_error(e.orig_exc)
1895
+ unpack_visit_error(e.orig_exc, text)
1823
1896
  elif isinstance(e.orig_exc, (UndefinedConceptException, ImportError)):
1824
1897
  raise e.orig_exc
1825
1898
  elif isinstance(e.orig_exc, (SyntaxError, TypeError)):
1826
1899
  if isinstance(e.obj, Tree):
1827
- raise InvalidSyntaxException(
1900
+ if text:
1901
+ extract = text[e.obj.meta.start_pos - 5 : e.obj.meta.end_pos + 5]
1902
+ raise InvalidSyntaxException(
1903
+ str(e.orig_exc)
1904
+ + " in "
1905
+ + str(e.rule)
1906
+ + f" Line: {e.obj.meta.line} ({extract})"
1907
+ )
1908
+ InvalidSyntaxException(
1828
1909
  str(e.orig_exc) + " in " + str(e.rule) + f" Line: {e.obj.meta.line}"
1829
1910
  )
1830
1911
  raise InvalidSyntaxException(str(e.orig_exc)).with_traceback(
@@ -1875,7 +1956,7 @@ def parse_text(
1875
1956
  f"Parse time: {end - start} for {len(text)} characters, {len(output)} objects"
1876
1957
  )
1877
1958
  except VisitError as e:
1878
- unpack_visit_error(e)
1959
+ unpack_visit_error(e, text)
1879
1960
  # this will never be reached
1880
1961
  raise e
1881
1962
  except (
@@ -1886,6 +1967,12 @@ def parse_text(
1886
1967
  ValidationError,
1887
1968
  TypeError,
1888
1969
  ) as e:
1970
+ if isinstance(
1971
+ e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput, UnexpectedToken)
1972
+ ):
1973
+ raise InvalidSyntaxException(
1974
+ str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
1975
+ )
1889
1976
  raise InvalidSyntaxException(str(e))
1890
1977
 
1891
1978
  return environment, output
@@ -52,7 +52,7 @@
52
52
 
53
53
  //column_assignment
54
54
  //figure out if we want static
55
- column_assignment: (raw_column_assignment | IDENTIFIER | QUOTED_IDENTIFIER | _static_functions ) ":" concept_assignment
55
+ column_assignment: (raw_column_assignment | IDENTIFIER | QUOTED_IDENTIFIER | expr ) ":" concept_assignment
56
56
 
57
57
  RAW_ENTRY.1: /raw\s*\(/s
58
58
 
@@ -102,7 +102,7 @@
102
102
  type_declaration: "type" IDENTIFIER data_type
103
103
 
104
104
  // user_id where state = Mexico
105
- _filter_alt: IDENTIFIER "?" conditional
105
+ _filter_alt: (IDENTIFIER | "(" expr ")") "?" conditional
106
106
  _filter_base: "filter"i IDENTIFIER where
107
107
  filter_item: _filter_base | _filter_alt
108
108
 
@@ -129,12 +129,6 @@
129
129
 
130
130
  limit: "LIMIT"i /[0-9]+/
131
131
 
132
- !window_order: /TOP|BOTTOM/i
133
-
134
- window: window_order /[0-9]+/
135
-
136
- window_order_by: "BY"i column_list
137
-
138
132
  order_list: expr ordering ("," expr ordering)* ","?
139
133
 
140
134
  over_list: concept_lit ("," concept_lit )* ","?
@@ -166,11 +160,9 @@
166
160
 
167
161
  !array_comparison: ( ("NOT"i "IN"i) | "IN"i)
168
162
 
169
- COMPARISON_OPERATOR: /(is\s+not|is|=|>=|<=|!=|>|<)/i
163
+ COMPARISON_OPERATOR: /(\s+is\s+not\s|\s+is\s|\s+in\s|\s+not\s+in\s|=|>=|<=|!=|>|<)/i
170
164
 
171
- comparison: expr COMPARISON_OPERATOR expr
172
-
173
- between_comparison: expr "between"i expr "and"i expr
165
+ between_comparison: "between"i expr "and"i expr
174
166
 
175
167
  subselect_comparison: expr array_comparison (literal | _constant_functions | _string_functions | concept_lit | filter_item | window_item | unnest | fgroup | expr_tuple | parenthetical )
176
168
 
@@ -187,31 +179,49 @@
187
179
  union: _UNION (expr ",")* expr ")"
188
180
 
189
181
  //indexing into an expression is a function
190
- index_access: expr "[" int_lit "]"
191
- map_key_access: expr "[" string_lit "]"
192
- attr_access: expr "." string_lit
182
+ index_access: atom "[" int_lit "]"
183
+ map_key_access: atom "[" string_lit "]"
184
+ attr_access: atom "." string_lit
193
185
 
186
+ ?expr: comparison_root | between_root
194
187
 
195
- expr: _basic_expr | _functional_expr | _operation_expr | _access_expr
188
+ ?comparison_root: sum_chain (COMPARISON_OPERATOR sum_chain)? -> comparison
189
+ ?between_root: sum_chain "between"i sum_chain "and"i sum_chain -> between_comparison
196
190
 
197
- # Most common/basic expressions
198
- _basic_expr: literal | concept_lit | parenthetical | expr_tuple
191
+ PLUS_OR_MINUS: ("+" | /-(?!>)/ | "||" | "like" )
199
192
 
200
- # Operations and comparisons
201
- _operation_expr: comparison | alt_like | between_comparison | subselect_comparison
193
+ ?sum_chain: product_chain (PLUS_OR_MINUS product_chain)* -> sum_operator
194
+
195
+ MULTIPLY_DIVIDE_PERCENT: ("*" | "/" | "%")
196
+
197
+ ?product_chain: atom ( MULTIPLY_DIVIDE_PERCENT atom)* -> product_operator
198
+
199
+ ?atom: literal | concept_lit | parenthetical
200
+ | expr_tuple
201
+ | custom_function
202
+ | _constant_functions
203
+ | _static_functions
204
+ | _generic_functions
205
+ | _date_functions
206
+ | aggregate_functions
207
+ | window_item
208
+ | unnest
209
+ | union
210
+ | fgroup
211
+ | filter_item
212
+ | _access_expr
213
+ | aggregate_by
202
214
 
203
- # Function-like expressions
204
- _functional_expr: _constant_functions | _static_functions | filter_item | aggregate_functions | window_item | custom_function | fgroup | unnest | union | aggregate_by
205
215
 
206
216
  # Access patterns
207
217
  _access_expr: index_access | map_key_access | attr_access
208
218
  // functions
209
219
 
210
- fadd: (/add\(/ expr "," expr ")" ) | ( expr "+" expr )
211
- fsub: ("subtract"i "(" expr "," expr ")" ) | ( expr "-" expr )
212
- fmul: ("multiply"i "(" expr "," expr ")" ) | ( expr "*" expr )
213
- fdiv: ( "divide"i "(" expr "," expr ")") | ( expr "/" expr )
214
- fmod: ( "mod"i "(" expr "," (int_lit | concept_lit ) ")") | ( expr "%" (int_lit | concept_lit ) )
220
+ fadd: (/add\(/ expr "," expr ")" )
221
+ fsub: ("subtract"i "(" expr "," expr ")" )
222
+ fmul: ("multiply"i "(" expr "," expr ")" )
223
+ fdiv: ( "divide"i "(" expr "," expr ")")
224
+ fmod: ( "mod"i "(" expr "," (int_lit | concept_lit ) ")")
215
225
  _ROUND.1: "round"i "("
216
226
  fround: _ROUND expr "," expr ")"
217
227
  fabs: "abs"i "(" expr ")"
@@ -224,9 +234,9 @@
224
234
 
225
235
  //generic
226
236
  _fcast_primary: "cast"i "(" expr "as"i data_type ")"
227
- _fcast_alt: expr "::" data_type
237
+ _fcast_alt: atom "::" data_type
228
238
  fcast: _fcast_primary | _fcast_alt
229
- concat: ("concat"i "(" (expr ",")* expr ")") | (expr "||" expr)
239
+ concat: ("concat"i "(" (expr ",")* expr ")")
230
240
  fcoalesce: "coalesce"i "(" (expr ",")* expr ")"
231
241
  fcase_when: "WHEN"i conditional "THEN"i expr
232
242
  fcase_else: "ELSE"i expr
@@ -235,7 +245,7 @@
235
245
  fnot: "NOT"i expr
236
246
  fbool: "bool"i "(" expr ")"
237
247
 
238
- _generic_functions: fcast | concat | fcoalesce | fcase | len | fnot | fbool
248
+ _generic_functions: fcast | concat | fcoalesce | fcase | len | fnot | fbool
239
249
 
240
250
  //constant
241
251
  CURRENT_DATE.1: /current_date\(\)/
@@ -333,12 +343,12 @@
333
343
 
334
344
  _date_functions: fdate | fdate_add | fdate_sub | fdate_diff | fdatetime | ftimestamp | fsecond | fminute | fhour | fday | fday_of_week | fweek | fmonth | fquarter | fyear | fdate_part | fdate_trunc
335
345
 
336
- _static_functions: _string_functions | _math_functions | _generic_functions | _date_functions
346
+ _static_functions: _string_functions | _math_functions
337
347
 
338
348
  custom_function: "@" IDENTIFIER "(" (expr ",")* expr ")"
339
349
 
340
350
  // base language constructs
341
- concept_lit: MINUS? IDENTIFIER
351
+ concept_lit: IDENTIFIER
342
352
  IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\.]*/
343
353
  WILDCARD_IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.\*]*/
344
354
  QUOTED_IDENTIFIER: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:\s]*`/
@@ -353,9 +363,9 @@
353
363
  _double_quote: "\"" ( DOUBLE_STRING_CHARS )* "\""
354
364
  string_lit: _single_quote | _double_quote | MULTILINE_STRING
355
365
 
356
- MINUS: "-"
357
366
 
358
- int_lit: MINUS? /[0-9]+/
367
+
368
+ int_lit: /\-?[0-9]+/
359
369
 
360
370
  float_lit: /[0-9]*\.[0-9]+/
361
371