pytrilogy 0.0.3.17__py3-none-any.whl → 0.0.3.19__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.2
2
2
  Name: pytrilogy
3
- Version: 0.0.3.17
3
+ Version: 0.0.3.19
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,4 +1,4 @@
1
- trilogy/__init__.py,sha256=Cc5BscM9uDohu_jL3zA-kHzLbnXCbHhN8P5XmRv78fA,303
1
+ trilogy/__init__.py,sha256=KxuTkRMFOlZrKTEJ94OxeHN92vhWtBys2MRzNG8uGBU,303
2
2
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  trilogy/constants.py,sha256=qZ1d0hoKPPV2HHCoFwPYTVB7b6bXjpWvXd3lE-zEhy8,1494
4
4
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
@@ -10,23 +10,23 @@ trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
10
10
  trilogy/authoring/__init__.py,sha256=ohkYA3_LGYZh3fwzEYKTN6ofACDI5GYl3VCbGxVvlzY,2233
11
11
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  trilogy/core/constants.py,sha256=7XaCpZn5mQmjTobbeBn56SzPWq9eMNDfzfsRU-fP0VE,171
13
- trilogy/core/enums.py,sha256=jOgUyoLGgC5-8SLBWP0jQDMxptBJbN0J48VsCa1EMYo,7111
13
+ trilogy/core/enums.py,sha256=ND69oja7DOsZS2T8JlIuDW2-uKm74x9SJOWbAqNeopU,7137
14
14
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
15
15
  trilogy/core/environment_helpers.py,sha256=oOpewPwMp8xOtx2ayeeyuLNUwr-cli7UanHKot5ebNY,7627
16
- trilogy/core/ergonomics.py,sha256=ASLDd0RqKWrZiG3XcKHo8nyTjaB_8xfE9t4NZ1UvGpc,1639
16
+ trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
17
17
  trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
18
- trilogy/core/functions.py,sha256=cY7Bg15CJJ0yk33MveYHbfGNdkXVZmVFB0fRnSbu9OQ,24929
18
+ trilogy/core/functions.py,sha256=E-OZZTshUNmEMMl8nhaUDUhQkr45gqUCdPaDiBlayic,25392
19
19
  trilogy/core/graph_models.py,sha256=z17EoO8oky2QOuO6E2aMWoVNKEVJFhLdsQZOhC4fNLU,2079
20
20
  trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
21
21
  trilogy/core/optimization.py,sha256=xGO8piVsLrpqrx-Aid_Y56_5slSv4eZmlP64hCHRiEc,7957
22
22
  trilogy/core/query_processor.py,sha256=Do8YpdPBdsbKtl9n37hobzk8SORMGqH-e_zNNxd-BE4,19456
23
23
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- trilogy/core/models/author.py,sha256=8IqKpTMIwOSdsEQdDuwmVn0BdoYn__-0ThA-CQqdk6Q,70734
24
+ trilogy/core/models/author.py,sha256=gsn_vkm8gvnwTvzgqOL5v3X1Lx4n8xg32tRwk_9Mxnc,73494
25
25
  trilogy/core/models/build.py,sha256=bO1qYvuGl6LeNGgsfS6ZHAzZBR2lBPLg-QJymp9hgkU,57235
26
26
  trilogy/core/models/build_environment.py,sha256=8UggvlPU708GZWYPJMc_ou2r7M3TY2g69eqGvz03YX0,5528
27
27
  trilogy/core/models/core.py,sha256=nb4h1HHm5_qwmUkYth4zRhEttS1EtsMZCP4vT20EEAE,10326
28
28
  trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
29
- trilogy/core/models/environment.py,sha256=WCVJNWUze37bxFSFWo_6Z7330OG5k0DDmb7a2Tz5gg4,26264
29
+ trilogy/core/models/environment.py,sha256=RlHNrRer4p1uSQM030iwGJL82M1hMyY5p8a550XTfUI,26606
30
30
  trilogy/core/models/execute.py,sha256=kxkw14vgUudHHELXLo73AHiaMEpLfiv0qAvbxjJxn_k,33929
31
31
  trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
32
32
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
@@ -64,7 +64,7 @@ trilogy/core/processing/nodes/union_node.py,sha256=fDFzLAUh5876X6_NM7nkhoMvHEdGJ
64
64
  trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
65
65
  trilogy/core/processing/nodes/window_node.py,sha256=STvwheVttxSWVHB-yUQUSo-Pyz7Uk8G1txFDAbWMp-s,1380
66
66
  trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
- trilogy/core/statements/author.py,sha256=ISLBb7c7oBJhpzsVJPUfmC-VqQk1BfsDQyl9Fpp53Io,14441
67
+ trilogy/core/statements/author.py,sha256=1cJ4nIBInTD3YcS89yTBda2ypghIhMvb-Mut66m_sxM,14615
68
68
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
69
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
70
70
  trilogy/core/statements/execute.py,sha256=cSlvpHFOqpiZ89pPZ5GDp9Hu6j6uj-5_h21FWm_L-KM,1248
@@ -74,7 +74,7 @@ trilogy/dialect/bigquery.py,sha256=PkjFcNGZHYOe655PmJhb8a0afdFULuovqP0qQVO8m0I,2
74
74
  trilogy/dialect/common.py,sha256=vYb-QPf_CnZ3mMLpOVjteWeLH1iaq2mn4WPx0XGoo20,4033
75
75
  trilogy/dialect/config.py,sha256=EGYRQIbrkeMuud5Bkds7jSD5dCJR5hEYZUYcy-lYZl4,3308
76
76
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
77
- trilogy/dialect/duckdb.py,sha256=AXpvheZQ_hCzTgTRAM5cxBZwr1N4XFzEGwRC1CGPxt4,3705
77
+ trilogy/dialect/duckdb.py,sha256=TepCOhYWYw1oUuOT6ZGlB3l4X6S8rYcldWe3zZm3HoU,3710
78
78
  trilogy/dialect/enums.py,sha256=QYIcVr5RgpYMA1Wl0nWeojVVxJxy0V2_sn8uqSFNx20,4615
79
79
  trilogy/dialect/postgres.py,sha256=VH4EB4myjIeZTHeFU6vK00GxY9c53rCBjg2mLbdaCEE,3254
80
80
  trilogy/dialect/presto.py,sha256=Mw7_F8h19mWfuZHkHQJizQWbpu1lIHe6t2PA0r88gsY,3392
@@ -90,14 +90,14 @@ trilogy/parsing/common.py,sha256=99tDKrpQTk-SpyTXUqKFtm-lfmhjCOQIn25hxbQvRRg,214
90
90
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
91
91
  trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
92
92
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
93
- trilogy/parsing/parse_engine.py,sha256=Qefenf8QS4YU5heW-f9M9gtGlrA4Qn-J5sJo9TCNmhw,60030
93
+ trilogy/parsing/parse_engine.py,sha256=AAzekSp9tcpI3lbfDoghB6zi7FkjLZIbZRSD9GLbDmU,59605
94
94
  trilogy/parsing/render.py,sha256=o_XuQWhcwx1lD9eGVqkqZEwkmQK0HdmWWokGBtdeH4I,17837
95
- trilogy/parsing/trilogy.lark,sha256=h7q1UBTBBhGbDTPWQ1COtf-Y41ZRHMP_B9iJ0WxLSfY,12831
95
+ trilogy/parsing/trilogy.lark,sha256=7libFS5HNiyHJYzr5_lEiY-Lpqit04_PgyIPHMZT7-w,12928
96
96
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
97
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
98
- pytrilogy-0.0.3.17.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
99
- pytrilogy-0.0.3.17.dist-info/METADATA,sha256=-oZ_8z9XToTcp54Xd-Ye7gd_DoVGut4sLgGE2NW04y0,8984
100
- pytrilogy-0.0.3.17.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
101
- pytrilogy-0.0.3.17.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
102
- pytrilogy-0.0.3.17.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
- pytrilogy-0.0.3.17.dist-info/RECORD,,
98
+ pytrilogy-0.0.3.19.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
99
+ pytrilogy-0.0.3.19.dist-info/METADATA,sha256=Gm_Cjipo1VE5HncqCyT3MNW3YLoLDAmV0P7pScqO_Ro,8984
100
+ pytrilogy-0.0.3.19.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
101
+ pytrilogy-0.0.3.19.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
102
+ pytrilogy-0.0.3.19.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
+ pytrilogy-0.0.3.19.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.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.17"
7
+ __version__ = "0.0.3.19"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/core/enums.py CHANGED
@@ -193,6 +193,7 @@ class FunctionType(Enum):
193
193
  DATE_PART = "date_part"
194
194
  DATE_TRUNCATE = "date_truncate"
195
195
  DATE_ADD = "date_add"
196
+ DATE_SUB = "date_sub"
196
197
  DATE_DIFF = "date_diff"
197
198
 
198
199
  # UNIX
@@ -3,7 +3,6 @@ from trilogy.constants import CONFIG
3
3
  # source: https://github.com/aaronbassett/Pass-phrase
4
4
  CTE_NAMES = """quizzical
5
5
  highfalutin
6
- dynamic
7
6
  wakeful
8
7
  cheerful
9
8
  thoughtful
trilogy/core/functions.py CHANGED
@@ -145,6 +145,9 @@ def get_date_trunc_output(
145
145
 
146
146
 
147
147
  FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
148
+ FunctionType.ALIAS: FunctionConfig(
149
+ arg_count=1,
150
+ ),
148
151
  FunctionType.PARENTHETICAL: FunctionConfig(
149
152
  arg_count=1,
150
153
  ),
@@ -375,6 +378,21 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
375
378
  output_type=DataType.DATE,
376
379
  arg_count=3,
377
380
  ),
381
+ FunctionType.DATE_SUB: FunctionConfig(
382
+ valid_inputs=[
383
+ {
384
+ DataType.DATE,
385
+ DataType.TIMESTAMP,
386
+ DataType.DATETIME,
387
+ DataType.STRING,
388
+ },
389
+ {DataType.DATE_PART},
390
+ {DataType.INTEGER},
391
+ ],
392
+ output_purpose=Purpose.PROPERTY,
393
+ output_type=DataType.DATE,
394
+ arg_count=3,
395
+ ),
378
396
  FunctionType.DATE_DIFF: FunctionConfig(
379
397
  valid_inputs=[
380
398
  {
@@ -639,8 +657,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
639
657
 
640
658
  EXCLUDED_FUNCTIONS = {
641
659
  FunctionType.CUSTOM,
642
- FunctionType.ALIAS,
643
- # FunctionType.PARENTHETICAL,
644
660
  # Temporary
645
661
  FunctionType.DATE_LITERAL,
646
662
  FunctionType.DATETIME_LITERAL,
@@ -649,7 +665,9 @@ EXCLUDED_FUNCTIONS = {
649
665
 
650
666
  for k in FunctionType.__members__.values():
651
667
  if k not in FUNCTION_REGISTRY and k not in EXCLUDED_FUNCTIONS:
652
- raise InvalidSyntaxException(f"Function {k} not in registry")
668
+ raise InvalidSyntaxException(
669
+ f"Function enum value {k} not in creation registry"
670
+ )
653
671
 
654
672
 
655
673
  class FunctionFactory:
@@ -318,6 +318,21 @@ class Conditional(Mergeable, ConceptArgs, Namespaced, BaseModel):
318
318
  operator=self.operator,
319
319
  )
320
320
 
321
+ def with_reference_replacement(self, source, target):
322
+ return self.__class__.model_construct(
323
+ left=(
324
+ self.left.with_reference_replacement(source, target)
325
+ if isinstance(self.left, Mergeable)
326
+ else self.left
327
+ ),
328
+ right=(
329
+ self.right.with_reference_replacement(source, target)
330
+ if isinstance(self.right, Mergeable)
331
+ else self.right
332
+ ),
333
+ operator=self.operator,
334
+ )
335
+
321
336
  @property
322
337
  def concept_arguments(self) -> Sequence[ConceptRef]:
323
338
  """Return concepts directly referenced in where clause"""
@@ -793,6 +808,10 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
793
808
  base = f"{self.address}@{self.grain}"
794
809
  return base
795
810
 
811
+ @property
812
+ def is_internal(self) -> bool:
813
+ return self.namespace.startswith("_") or self.name.startswith("_")
814
+
796
815
  @property
797
816
  def reference(self) -> ConceptRef:
798
817
  return ConceptRef.model_construct(
@@ -2131,6 +2150,52 @@ class AlignItem(Namespaced, BaseModel):
2131
2150
  )
2132
2151
 
2133
2152
 
2153
+ class CustomFunctionFactory:
2154
+ def __init__(
2155
+ self, function: Expr, namespace: str, function_arguments: list[ArgBinding]
2156
+ ):
2157
+ self.namespace = namespace
2158
+ self.function = function
2159
+ self.function_arguments = function_arguments
2160
+
2161
+ def with_namespace(self, namespace: str):
2162
+ self.namespace = namespace
2163
+ self.function = (
2164
+ self.function.with_namespace(namespace)
2165
+ if isinstance(self.function, Namespaced)
2166
+ else self.function
2167
+ )
2168
+ self.function_arguments = [
2169
+ x.with_namespace(namespace) for x in self.function_arguments
2170
+ ]
2171
+ return self
2172
+
2173
+ def __call__(self, *creation_args: list[Expr]):
2174
+ nout = (
2175
+ self.function.model_copy(deep=True)
2176
+ if isinstance(self.function, BaseModel)
2177
+ else self.function
2178
+ )
2179
+ creation_arg_list: list[Expr] = list(creation_args)
2180
+ if len(creation_args) < len(self.function_arguments):
2181
+ for binding in self.function_arguments[len(creation_arg_list) :]:
2182
+ if binding.default is None:
2183
+ raise ValueError(f"Missing argument {binding.name}")
2184
+ creation_arg_list.append(binding.default)
2185
+ if isinstance(nout, Mergeable):
2186
+ for idx, x in enumerate(creation_arg_list):
2187
+ if self.namespace == DEFAULT_NAMESPACE:
2188
+ target = f"{DEFAULT_NAMESPACE}.{self.function_arguments[idx].name}"
2189
+ else:
2190
+ target = self.function_arguments[idx].name
2191
+ nout = (
2192
+ nout.with_reference_replacement(target, x)
2193
+ if isinstance(nout, Mergeable)
2194
+ else nout
2195
+ )
2196
+ return nout
2197
+
2198
+
2134
2199
  class Metadata(BaseModel):
2135
2200
  """Metadata container object.
2136
2201
  TODO: support arbitrary tags"""
@@ -2160,10 +2225,20 @@ class Comment(BaseModel):
2160
2225
  text: str
2161
2226
 
2162
2227
 
2163
- class ArgBinding(BaseModel):
2228
+ class ArgBinding(Namespaced, BaseModel):
2164
2229
  name: str
2165
2230
  default: Expr | None = None
2166
2231
 
2232
+ def with_namespace(self, namespace):
2233
+ return ArgBinding(
2234
+ name=address_with_namespace(self.name, namespace),
2235
+ default=(
2236
+ self.default.with_namespace(namespace)
2237
+ if isinstance(self.default, Namespaced)
2238
+ else self.default
2239
+ ),
2240
+ )
2241
+
2167
2242
 
2168
2243
  class CustomType(BaseModel):
2169
2244
  name: str
@@ -9,7 +9,6 @@ from typing import (
9
9
  TYPE_CHECKING,
10
10
  Annotated,
11
11
  Any,
12
- Callable,
13
12
  Dict,
14
13
  ItemsView,
15
14
  List,
@@ -40,6 +39,7 @@ from trilogy.core.exceptions import (
40
39
  from trilogy.core.models.author import (
41
40
  Concept,
42
41
  ConceptRef,
42
+ CustomFunctionFactory,
43
43
  CustomType,
44
44
  Function,
45
45
  SelectLineage,
@@ -58,6 +58,7 @@ if TYPE_CHECKING:
58
58
  class Import:
59
59
  alias: str
60
60
  path: Path
61
+ input_path: str | None = None
61
62
 
62
63
 
63
64
  class BaseImportResolver(BaseModel):
@@ -206,7 +207,7 @@ class Environment(BaseModel):
206
207
  datasources: Annotated[
207
208
  EnvironmentDatasourceDict, PlainValidator(validate_datasources)
208
209
  ] = Field(default_factory=EnvironmentDatasourceDict)
209
- functions: Dict[str, Callable[..., Any]] = Field(default_factory=dict)
210
+ functions: Dict[str, CustomFunctionFactory] = Field(default_factory=dict)
210
211
  data_types: Dict[str, CustomType] = Field(default_factory=dict)
211
212
  named_statements: Dict[str, SelectLineage] = Field(default_factory=dict)
212
213
  imports: Dict[str, list[Import]] = Field(
@@ -433,6 +434,14 @@ class Environment(BaseModel):
433
434
  self.alias_origin_lookup[address_with_namespace(key, alias)] = (
434
435
  val.with_namespace(alias)
435
436
  )
437
+
438
+ for key, function in source.functions.items():
439
+ if same_namespace:
440
+ self.functions[key] = function
441
+ else:
442
+ self.functions[address_with_namespace(key, alias)] = (
443
+ function.with_namespace(alias)
444
+ )
436
445
  return self
437
446
 
438
447
  def add_file_import(
@@ -382,7 +382,12 @@ class MergeStatementV2(HasUUID, BaseModel):
382
382
 
383
383
 
384
384
  class ImportStatement(HasUUID, BaseModel):
385
+ # import abc.def as bar
386
+ # the bit after 'as', eg bar
385
387
  alias: str
388
+ # the bit after import, abc.def
389
+ input_path: str
390
+ # what it actually resolves to, typically a filepath
386
391
  path: Path
387
392
 
388
393
 
trilogy/dialect/duckdb.py CHANGED
@@ -8,6 +8,7 @@ from trilogy.dialect.base import BaseDialect
8
8
 
9
9
  WINDOW_FUNCTION_MAP: Mapping[WindowType, Callable[[Any, Any, Any], str]] = {}
10
10
 
11
+
11
12
  FUNCTION_MAP = {
12
13
  FunctionType.COUNT: lambda args: f"count({args[0]})",
13
14
  FunctionType.SUM: lambda args: f"sum({args[0]})",
@@ -27,7 +28,7 @@ FUNCTION_MAP = {
27
28
  # datetime is aliased
28
29
  FunctionType.CURRENT_DATETIME: lambda x: "cast(get_current_timestamp() as datetime)",
29
30
  FunctionType.DATE_TRUNCATE: lambda x: f"date_trunc('{x[1]}', {x[0]})",
30
- FunctionType.DATE_ADD: lambda x: f"date_add({x[0]}, INTERVAL {x[2]} {x[1]})",
31
+ FunctionType.DATE_ADD: lambda x: f"date_add({x[0]}, {x[2]} * INTERVAL 1 {x[1]})",
31
32
  FunctionType.DATE_PART: lambda x: f"date_part('{x[1]}', {x[0]})",
32
33
  FunctionType.DATE_DIFF: lambda x: f"date_diff('{x[2]}', {x[0]}, {x[1]})",
33
34
  FunctionType.CONCAT: lambda x: f"({' || '.join(x)})",
@@ -57,13 +57,13 @@ from trilogy.core.models.author import (
57
57
  Concept,
58
58
  ConceptRef,
59
59
  Conditional,
60
+ CustomFunctionFactory,
60
61
  CustomType,
61
62
  Expr,
62
63
  FilterItem,
63
64
  Function,
64
65
  Grain,
65
66
  HavingClause,
66
- Mergeable,
67
67
  Metadata,
68
68
  OrderBy,
69
69
  OrderItem,
@@ -951,7 +951,8 @@ class ParseToObjects(Transformer):
951
951
  else:
952
952
  alias = self.environment.namespace
953
953
  cache_key = args[0]
954
- path = args[0].split(".")
954
+ input_path = args[0]
955
+ path = input_path.split(".")
955
956
 
956
957
  if isinstance(
957
958
  self.environment.config.import_resolver, FileSystemImportResolver
@@ -971,7 +972,9 @@ class ParseToObjects(Transformer):
971
972
 
972
973
  # we don't iterate past the max parse depth
973
974
  if len(key_path) > MAX_PARSE_DEPTH:
974
- return ImportStatement(alias=alias, path=Path(target))
975
+ return ImportStatement(
976
+ alias=alias, input_path=input_path, path=Path(target)
977
+ )
975
978
 
976
979
  if token_lookup in self.tokens:
977
980
  raw_tokens = self.tokens[token_lookup]
@@ -1015,7 +1018,7 @@ class ParseToObjects(Transformer):
1015
1018
  ) from e
1016
1019
 
1017
1020
  parsed_path = Path(args[0])
1018
- imps = ImportStatement(alias=alias, path=parsed_path)
1021
+ imps = ImportStatement(alias=alias, input_path=input_path, path=parsed_path)
1019
1022
 
1020
1023
  self.environment.add_import(
1021
1024
  alias, new_env, Import(alias=alias, path=parsed_path)
@@ -1195,23 +1198,11 @@ class ParseToObjects(Transformer):
1195
1198
  function_arguments: list[ArgBinding] = args[1]
1196
1199
  output = args[2]
1197
1200
 
1198
- def function_factory(*creation_args: list[Expr]):
1199
- nout = output.copy(deep=True)
1200
- creation_arg_list: list[Expr] = list(creation_args)
1201
- if len(creation_args) < len(function_arguments):
1202
- for binding in function_arguments[len(creation_arg_list) :]:
1203
- if binding.default is None:
1204
- raise ValueError(f"Missing argument {binding.name}")
1205
- creation_arg_list.append(binding.default)
1206
- if isinstance(nout, Mergeable):
1207
- for idx, x in enumerate(creation_arg_list):
1208
- # these will always be local namespace
1209
- nout = nout.with_reference_replacement(
1210
- f"{DEFAULT_NAMESPACE}.{function_arguments[idx].name}", x
1211
- )
1212
- return nout
1213
-
1214
- self.environment.functions[identity] = function_factory
1201
+ self.environment.functions[identity] = CustomFunctionFactory(
1202
+ function=output,
1203
+ namespace=self.environment.namespace,
1204
+ function_arguments=function_arguments,
1205
+ )
1215
1206
  return FunctionDeclaration(name=identity, args=function_arguments, expr=output)
1216
1207
 
1217
1208
  def custom_function(self, args):
@@ -1582,6 +1573,10 @@ class ParseToObjects(Transformer):
1582
1573
  def fdate_add(self, meta, args):
1583
1574
  return self.function_factory.create_function(args, FunctionType.DATE_ADD, meta)
1584
1575
 
1576
+ @v_args(meta=True)
1577
+ def fdate_sub(self, meta, args):
1578
+ return self.function_factory.create_function(args, FunctionType.DATE_SUB, meta)
1579
+
1585
1580
  @v_args(meta=True)
1586
1581
  def fdate_diff(self, meta, args):
1587
1582
  return self.function_factory.create_function(args, FunctionType.DATE_DIFF, meta)
@@ -293,11 +293,13 @@
293
293
  _DATE_PART.1: "date_part("i
294
294
  fdate_part: _DATE_PART expr "," DATE_PART ")"
295
295
  _DATE_ADD.1: "date_add("i
296
- fdate_add: _DATE_ADD expr "," DATE_PART "," int_lit ")"
297
- _DATE_DIFF.1: "date_diff("i
296
+ fdate_add: _DATE_ADD expr "," DATE_PART "," expr ")"
297
+ _DATE_SUB.1: "date_sub("i
298
+ fdate_sub: _DATE_SUB expr "," DATE_PART "," expr ")"
299
+ _DATE_DIFF.1: "date_diff("i
298
300
  fdate_diff: _DATE_DIFF expr "," expr "," DATE_PART ")"
299
301
 
300
- _date_functions: fdate | fdate_add | fdate_diff | fdatetime | ftimestamp | fsecond | fminute | fhour | fday | fday_of_week | fweek | fmonth | fquarter | fyear | fdate_part | fdate_trunc
302
+ _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
301
303
 
302
304
  _static_functions: _string_functions | _math_functions | _generic_functions | _date_functions
303
305