pytrilogy 0.0.3.16__py3-none-any.whl → 0.0.3.18__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.16
3
+ Version: 0.0.3.18
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=4fwdD6idpRmJ7XtHqlcxrKzhBPI4tVYCM-NldzFMJIY,303
1
+ trilogy/__init__.py,sha256=jrbWIot3RjwpdU7heWZocVfCD4BBtWw40jekbkQLvdg,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
@@ -7,26 +7,26 @@ trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
7
7
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  trilogy/render.py,sha256=D6rI1RNtn0StJeSe4e18lnlc-U--cNu4lh5C_NkU_uM,1218
9
9
  trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
10
- trilogy/authoring/__init__.py,sha256=6KPn4uoPb_7t-i510M8UNXB7nrPpaKlxAXwuRbWBdLE,2115
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=EsRYHE2kg_FckceVgYGPPs1ylrXvBjr3l1Wa6r0SvL8,25027
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=CHVnj-GW_gjGDHaAUg60VGOfPSRr_5n5LbHvocOhQj8,70371
24
+ trilogy/core/models/author.py,sha256=u2wS2rg3Qsqb5xNRUYvjgA7jOwQS2dMZzytlwJtglBs,70859
25
25
  trilogy/core/models/build.py,sha256=bO1qYvuGl6LeNGgsfS6ZHAzZBR2lBPLg-QJymp9hgkU,57235
26
26
  trilogy/core/models/build_environment.py,sha256=8UggvlPU708GZWYPJMc_ou2r7M3TY2g69eqGvz03YX0,5528
27
- trilogy/core/models/core.py,sha256=00opIUXwgJy9OF-cwI883zQpArNeh6wkTpSqUszDU78,9909
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=4w1a9iHdRuOuSoZxz5Kzoy61Vjj4TZVXzKkCSP6c-Ow,26298
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=X3NdGlWTUCNdQQw9EGI1kccgsLJMazwNAIsqpUSr_ZY,14443
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
@@ -86,18 +86,18 @@ trilogy/hooks/graph_hook.py,sha256=c-vC-IXoJ_jDmKQjxQyIxyXPOuUcLIURB573gCsAfzQ,2
86
86
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
87
87
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
88
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
- trilogy/parsing/common.py,sha256=IgZ2K3LzJ0usLIwxRCRmS-4luP6uwmM-f1oqGNyGbm0,21306
89
+ trilogy/parsing/common.py,sha256=99tDKrpQTk-SpyTXUqKFtm-lfmhjCOQIn25hxbQvRRg,21459
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=YjYe4cAxek3CceewGI1It-XEO_ZygCxY9IQzcJw0Y0Y,57555
93
+ trilogy/parsing/parse_engine.py,sha256=Hodpr2QjJNHSS5xGP-wPFjKK9gpygGG6loCikyRiwH8,60287
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.16.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
99
- pytrilogy-0.0.3.16.dist-info/METADATA,sha256=bEP9eVWQb7cplZ0EeB5fBluzVoMy_jLd0W1X3ZmrGrs,8984
100
- pytrilogy-0.0.3.16.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
101
- pytrilogy-0.0.3.16.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
102
- pytrilogy-0.0.3.16.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
- pytrilogy-0.0.3.16.dist-info/RECORD,,
98
+ pytrilogy-0.0.3.18.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
99
+ pytrilogy-0.0.3.18.dist-info/METADATA,sha256=V6EpL5Z-9Xief5rqKaourYFLzmRMZH9jM31orwdgYAk,8984
100
+ pytrilogy-0.0.3.18.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
101
+ pytrilogy-0.0.3.18.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
102
+ pytrilogy-0.0.3.18.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
103
+ pytrilogy-0.0.3.18.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.16"
7
+ __version__ = "0.0.3.18"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -40,6 +40,7 @@ from trilogy.core.models.core import (
40
40
  MapType,
41
41
  StructType,
42
42
  )
43
+ from trilogy.core.models.datasource import Datasource, DatasourceMetadata
43
44
  from trilogy.core.models.environment import Environment
44
45
  from trilogy.core.statements.author import (
45
46
  ConceptDeclarationStatement,
@@ -100,4 +101,6 @@ __all__ = [
100
101
  "MultiSelectStatement",
101
102
  "PersistStatement",
102
103
  "RawSQLStatement",
104
+ "Datasource",
105
+ "DatasourceMetadata",
103
106
  ]
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
@@ -29,6 +29,7 @@ from trilogy.core.models.core import (
29
29
  MapType,
30
30
  NumericType,
31
31
  StructType,
32
+ TraitDataType,
32
33
  arg_to_datatype,
33
34
  merge_datatypes,
34
35
  )
@@ -94,6 +95,10 @@ def get_cast_output_type(
94
95
  return args[1]
95
96
 
96
97
 
98
+ def get_output_type_at_index(args, index: int):
99
+ return arg_to_datatype(args[index])
100
+
101
+
97
102
  def validate_case_output(
98
103
  args: list[Any],
99
104
  ) -> DataType:
@@ -140,6 +145,12 @@ def get_date_trunc_output(
140
145
 
141
146
 
142
147
  FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
148
+ FunctionType.ALIAS: FunctionConfig(
149
+ arg_count=1,
150
+ ),
151
+ FunctionType.PARENTHETICAL: FunctionConfig(
152
+ arg_count=1,
153
+ ),
143
154
  FunctionType.UNNEST: FunctionConfig(
144
155
  valid_inputs={
145
156
  DataType.ARRAY,
@@ -173,7 +184,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
173
184
  DataType.BOOL,
174
185
  },
175
186
  output_purpose=Purpose.METRIC,
176
- output_type=DataType.INTEGER,
177
187
  arg_count=1,
178
188
  ),
179
189
  FunctionType.MIN: FunctionConfig(
@@ -186,7 +196,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
186
196
  DataType.TIMESTAMP,
187
197
  },
188
198
  output_purpose=Purpose.METRIC,
189
- output_type=DataType.INTEGER,
190
199
  arg_count=1,
191
200
  ),
192
201
  FunctionType.SPLIT: FunctionConfig(
@@ -237,7 +246,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
237
246
  FunctionType.ABS: FunctionConfig(
238
247
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
239
248
  output_purpose=Purpose.PROPERTY,
240
- output_type=DataType.INTEGER,
241
249
  arg_count=1,
242
250
  ),
243
251
  FunctionType.COALESCE: FunctionConfig(
@@ -370,6 +378,21 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
370
378
  output_type=DataType.DATE,
371
379
  arg_count=3,
372
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
+ ),
373
396
  FunctionType.DATE_DIFF: FunctionConfig(
374
397
  valid_inputs=[
375
398
  {
@@ -519,7 +542,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
519
542
  DataType.NUMERIC,
520
543
  },
521
544
  output_purpose=Purpose.PROPERTY,
522
- output_type=DataType.INTEGER,
523
545
  arg_count=InfiniteFunctionArgs,
524
546
  ),
525
547
  FunctionType.SUBTRACT: FunctionConfig(
@@ -530,7 +552,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
530
552
  DataType.NUMERIC,
531
553
  },
532
554
  output_purpose=Purpose.PROPERTY,
533
- output_type=DataType.INTEGER,
534
555
  arg_count=InfiniteFunctionArgs,
535
556
  ),
536
557
  FunctionType.MULTIPLY: FunctionConfig(
@@ -541,7 +562,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
541
562
  DataType.NUMERIC,
542
563
  },
543
564
  output_purpose=Purpose.PROPERTY,
544
- output_type=DataType.INTEGER,
545
565
  arg_count=InfiniteFunctionArgs,
546
566
  ),
547
567
  FunctionType.DIVIDE: FunctionConfig(
@@ -552,7 +572,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
552
572
  DataType.NUMERIC,
553
573
  },
554
574
  output_purpose=Purpose.PROPERTY,
555
- output_type=DataType.INTEGER,
556
575
  arg_count=InfiniteFunctionArgs,
557
576
  ),
558
577
  FunctionType.MOD: FunctionConfig(
@@ -570,7 +589,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
570
589
  {DataType.INTEGER},
571
590
  ],
572
591
  output_purpose=Purpose.PROPERTY,
573
- output_type=DataType.INTEGER,
592
+ output_type_function=lambda args: get_output_type_at_index(args, 0),
574
593
  arg_count=2,
575
594
  ),
576
595
  FunctionType.CUSTOM: FunctionConfig(
@@ -621,13 +640,11 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
621
640
  FunctionType.SUM: FunctionConfig(
622
641
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
623
642
  output_purpose=Purpose.METRIC,
624
- output_type=DataType.INTEGER,
625
643
  arg_count=1,
626
644
  ),
627
645
  FunctionType.AVG: FunctionConfig(
628
646
  valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
629
647
  output_purpose=Purpose.METRIC,
630
- output_type=DataType.INTEGER,
631
648
  arg_count=1,
632
649
  ),
633
650
  FunctionType.UNIX_TO_TIMESTAMP: FunctionConfig(
@@ -640,8 +657,6 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
640
657
 
641
658
  EXCLUDED_FUNCTIONS = {
642
659
  FunctionType.CUSTOM,
643
- FunctionType.ALIAS,
644
- FunctionType.PARENTHETICAL,
645
660
  # Temporary
646
661
  FunctionType.DATE_LITERAL,
647
662
  FunctionType.DATETIME_LITERAL,
@@ -650,7 +665,9 @@ EXCLUDED_FUNCTIONS = {
650
665
 
651
666
  for k in FunctionType.__members__.values():
652
667
  if k not in FUNCTION_REGISTRY and k not in EXCLUDED_FUNCTIONS:
653
- raise InvalidSyntaxException(f"Function {k} not in registry")
668
+ raise InvalidSyntaxException(
669
+ f"Function enum value {k} not in creation registry"
670
+ )
654
671
 
655
672
 
656
673
  class FunctionFactory:
@@ -686,8 +703,10 @@ class FunctionFactory:
686
703
  full_args = []
687
704
  final_output_type: CONCRETE_TYPES
688
705
  if config.output_type_function:
706
+
689
707
  final_output_type = config.output_type_function(full_args)
690
708
  elif not base_output_type:
709
+
691
710
  final_output_type = merge_datatypes([arg_to_datatype(x) for x in full_args])
692
711
  elif base_output_type:
693
712
  final_output_type = base_output_type
@@ -715,7 +734,7 @@ def create_function_derived_concept(
715
734
  operator: FunctionType,
716
735
  arguments: list[Concept],
717
736
  output_type: Optional[
718
- DataType | ListType | StructType | MapType | NumericType
737
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
719
738
  ] = None,
720
739
  output_purpose: Optional[Purpose] = None,
721
740
  ) -> Concept:
@@ -793,6 +793,10 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
793
793
  base = f"{self.address}@{self.grain}"
794
794
  return base
795
795
 
796
+ @property
797
+ def is_internal(self) -> bool:
798
+ return self.namespace.startswith("_") or self.name.startswith("_")
799
+
796
800
  @property
797
801
  def reference(self) -> ConceptRef:
798
802
  return ConceptRef.model_construct(
@@ -1370,7 +1374,7 @@ class WindowItem(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1370
1374
 
1371
1375
 
1372
1376
  def get_basic_type(
1373
- type: DataType | ListType | StructType | MapType | NumericType,
1377
+ type: DataType | ListType | StructType | MapType | NumericType | TraitDataType,
1374
1378
  ) -> DataType:
1375
1379
  if isinstance(type, ListType):
1376
1380
  return DataType.LIST
@@ -1380,6 +1384,8 @@ def get_basic_type(
1380
1384
  return DataType.MAP
1381
1385
  if isinstance(type, NumericType):
1382
1386
  return DataType.NUMERIC
1387
+ if isinstance(type, TraitDataType):
1388
+ return type.type
1383
1389
  return type
1384
1390
 
1385
1391
 
@@ -1531,7 +1537,9 @@ def get_concept_arguments(expr) -> List["ConceptRef"]:
1531
1537
  class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1532
1538
  operator: FunctionType
1533
1539
  arg_count: int = Field(default=1)
1534
- output_datatype: DataType | ListType | StructType | MapType | NumericType
1540
+ output_datatype: (
1541
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
1542
+ )
1535
1543
  output_purpose: Purpose
1536
1544
  valid_inputs: Optional[
1537
1545
  Union[
@@ -1629,23 +1637,30 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1629
1637
  return v
1630
1638
 
1631
1639
  def with_reference_replacement(self, source: str, target: Expr):
1640
+ from trilogy.core.functions import arg_to_datatype, merge_datatypes
1641
+
1642
+ nargs = [
1643
+ (
1644
+ c.with_reference_replacement(
1645
+ source,
1646
+ target,
1647
+ )
1648
+ if isinstance(
1649
+ c,
1650
+ Mergeable,
1651
+ )
1652
+ else c
1653
+ )
1654
+ for c in self.arguments
1655
+ ]
1656
+ if self.output_datatype == DataType.UNKNOWN:
1657
+ new_output = merge_datatypes([arg_to_datatype(x) for x in nargs])
1658
+ else:
1659
+ new_output = self.output_datatype
1632
1660
  return Function.model_construct(
1633
1661
  operator=self.operator,
1634
- arguments=[
1635
- (
1636
- c.with_reference_replacement(
1637
- source,
1638
- target,
1639
- )
1640
- if isinstance(
1641
- c,
1642
- Mergeable,
1643
- )
1644
- else c
1645
- )
1646
- for c in self.arguments
1647
- ],
1648
- output_datatype=self.output_datatype,
1662
+ arguments=nargs,
1663
+ output_datatype=new_output,
1649
1664
  output_purpose=self.output_purpose,
1650
1665
  valid_inputs=self.valid_inputs,
1651
1666
  arg_count=self.arg_count,
@@ -1777,7 +1792,7 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
1777
1792
  )
1778
1793
 
1779
1794
 
1780
- class FilterItem(Namespaced, ConceptArgs, BaseModel):
1795
+ class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
1781
1796
  content: ConceptRef
1782
1797
  where: "WhereClause"
1783
1798
 
@@ -1837,7 +1852,7 @@ class RowsetLineage(Namespaced, Mergeable, BaseModel):
1837
1852
  )
1838
1853
 
1839
1854
 
1840
- class RowsetItem(Mergeable, ConceptArgs, Namespaced, BaseModel):
1855
+ class RowsetItem(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel):
1841
1856
  content: ConceptRef
1842
1857
  rowset: RowsetLineage
1843
1858
 
@@ -51,7 +51,13 @@ class Addressable(ABC):
51
51
 
52
52
 
53
53
  TYPEDEF_TYPES = Union[
54
- "DataType", "MapType", "ListType", "NumericType", "StructType", "DataTyped"
54
+ "DataType",
55
+ "MapType",
56
+ "ListType",
57
+ "NumericType",
58
+ "StructType",
59
+ "DataTyped",
60
+ "TraitDataType",
55
61
  ]
56
62
 
57
63
  CONCRETE_TYPES = Union[
@@ -60,6 +66,7 @@ CONCRETE_TYPES = Union[
60
66
  "ListType",
61
67
  "NumericType",
62
68
  "StructType",
69
+ "TraitDataType",
63
70
  ]
64
71
 
65
72
  KT = TypeVar("KT")
@@ -101,6 +108,16 @@ class TraitDataType(BaseModel):
101
108
  type: DataType
102
109
  traits: list[str]
103
110
 
111
+ def __hash__(self):
112
+ return hash(self.type)
113
+
114
+ def __eq__(self, other):
115
+ if isinstance(other, DataType):
116
+ return self.type == other
117
+ elif isinstance(other, TraitDataType):
118
+ return self.type == other.type and self.traits == other.traits
119
+ return False
120
+
104
121
  @property
105
122
  def data_type(self):
106
123
  return self.type
@@ -312,8 +329,10 @@ def dict_to_map_wrapper(arg):
312
329
 
313
330
 
314
331
  def merge_datatypes(
315
- inputs: list[DataType | ListType | StructType | MapType | NumericType],
316
- ) -> DataType | ListType | StructType | MapType | NumericType:
332
+ inputs: list[
333
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
334
+ ],
335
+ ) -> DataType | ListType | StructType | MapType | NumericType | TraitDataType:
317
336
  """This is a temporary hack for doing between
318
337
  allowable datatype transformation matrix"""
319
338
  if len(inputs) == 1:
@@ -348,6 +367,7 @@ def is_compatible_datatype(left, right):
348
367
 
349
368
 
350
369
  def arg_to_datatype(arg) -> CONCRETE_TYPES:
370
+
351
371
  if isinstance(arg, MagicConstants):
352
372
  if arg == MagicConstants.NULL:
353
373
  return DataType.NULL
@@ -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):
@@ -134,7 +134,6 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
134
134
  output.grain = output.calculate_grain(environment)
135
135
 
136
136
  for x in selection:
137
-
138
137
  if x.is_undefined and environment.concepts.fail_on_missing:
139
138
  environment.concepts.raise_undefined(
140
139
  x.concept.address, meta.line_number if meta else None
@@ -158,7 +157,6 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
158
157
  output.local_concepts[x.content.address] = environment.concepts[
159
158
  x.content.address
160
159
  ] # .set_select_grain(output.grain, environment)
161
-
162
160
  output.validate_syntax(environment)
163
161
  return output
164
162
 
@@ -384,7 +382,12 @@ class MergeStatementV2(HasUUID, BaseModel):
384
382
 
385
383
 
386
384
  class ImportStatement(HasUUID, BaseModel):
385
+ # import abc.def as bar
386
+ # the bit after 'as', eg bar
387
387
  alias: str
388
+ # the bit after import, abc.def
389
+ input_path: str
390
+ # what it actually resolves to, typically a filepath
388
391
  path: Path
389
392
 
390
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)})",
trilogy/parsing/common.py CHANGED
@@ -100,6 +100,8 @@ def process_function_arg(
100
100
  concept.metadata.line_number = meta.line
101
101
  environment.add_concept(concept, meta=meta)
102
102
  return concept
103
+ elif isinstance(arg, ConceptRef):
104
+ return environment.concepts[arg.address]
103
105
  return arg
104
106
 
105
107
 
@@ -192,6 +194,8 @@ def concept_is_relevant(
192
194
  ) -> bool:
193
195
  if isinstance(concept, UndefinedConcept):
194
196
 
197
+ return False
198
+ if concept.datatype == DataType.UNKNOWN:
195
199
  return False
196
200
  if isinstance(concept, ConceptRef):
197
201
  if environment:
@@ -4,7 +4,7 @@ from enum import Enum
4
4
  from os.path import dirname, join
5
5
  from pathlib import Path
6
6
  from re import IGNORECASE
7
- from typing import List, Optional, Tuple, Union
7
+ from typing import Any, List, Optional, Tuple, Union
8
8
 
9
9
  from lark import Lark, ParseTree, Transformer, Tree, v_args
10
10
  from lark.exceptions import (
@@ -68,6 +68,7 @@ from trilogy.core.models.author import (
68
68
  OrderBy,
69
69
  OrderItem,
70
70
  Parenthetical,
71
+ RowsetItem,
71
72
  SubselectComparison,
72
73
  WhereClause,
73
74
  Window,
@@ -77,6 +78,7 @@ from trilogy.core.models.author import (
77
78
  )
78
79
  from trilogy.core.models.core import (
79
80
  DataType,
81
+ DataTyped,
80
82
  ListType,
81
83
  ListWrapper,
82
84
  MapType,
@@ -223,6 +225,52 @@ def unwrap_transformation(
223
225
  )
224
226
 
225
227
 
228
+ def rehydrate_lineage(
229
+ lineage: Any, environment: Environment, function_factory: FunctionFactory
230
+ ) -> Any:
231
+ """Fix datatype propagation. This is a hack to fix the fact that we don't know the datatypes of functions until we've parsed all concepts"""
232
+ if isinstance(lineage, Function):
233
+ rehydrated = [
234
+ rehydrate_lineage(x, environment, function_factory)
235
+ for x in lineage.arguments
236
+ ]
237
+ return function_factory.create_function(
238
+ rehydrated,
239
+ operator=lineage.operator,
240
+ )
241
+ elif isinstance(lineage, Parenthetical):
242
+ lineage.content = rehydrate_lineage(
243
+ lineage.content, environment, function_factory
244
+ )
245
+ return lineage
246
+ elif isinstance(lineage, WindowItem):
247
+ lineage.content.datatype = environment.concepts[
248
+ lineage.content.address
249
+ ].datatype
250
+ return lineage
251
+ elif isinstance(lineage, AggregateWrapper):
252
+ lineage.function = rehydrate_lineage(
253
+ lineage.function, environment, function_factory
254
+ )
255
+ return lineage
256
+ elif isinstance(lineage, RowsetItem):
257
+ lineage.content.datatype = environment.concepts[
258
+ lineage.content.address
259
+ ].datatype
260
+ return lineage
261
+ else:
262
+ return lineage
263
+
264
+
265
+ def rehydrate_concept_lineage(
266
+ concept: Concept, environment: Environment, function_factory: FunctionFactory
267
+ ) -> Concept:
268
+ concept.lineage = rehydrate_lineage(concept.lineage, environment, function_factory)
269
+ if isinstance(concept.lineage, DataTyped):
270
+ concept.datatype = concept.lineage.output_datatype
271
+ return concept
272
+
273
+
226
274
  class ParseToObjects(Transformer):
227
275
  def __init__(
228
276
  self,
@@ -275,6 +323,25 @@ class ParseToObjects(Transformer):
275
323
  v.run_second_parse_pass()
276
324
  reparsed = self.transform(self.tokens[self.token_address])
277
325
  self.environment.concepts.undefined = {}
326
+ passed = False
327
+ passes = 0
328
+ # output datatypes for functions may have been wrong
329
+ # as they were derived from not fully understood upstream types
330
+ # so loop through to recreate function lineage until all datatypes are known
331
+
332
+ while not passed:
333
+ new_passed = True
334
+ for x, y in self.environment.concepts.items():
335
+ if y.datatype == DataType.UNKNOWN and y.lineage:
336
+ self.environment.concepts[x] = rehydrate_concept_lineage(
337
+ y, self.environment, self.function_factory
338
+ )
339
+ new_passed = False
340
+ passes += 1
341
+ if passes > MAX_PARSE_DEPTH:
342
+ break
343
+ passed = new_passed
344
+
278
345
  return reparsed
279
346
 
280
347
  def start(self, args):
@@ -884,7 +951,8 @@ class ParseToObjects(Transformer):
884
951
  else:
885
952
  alias = self.environment.namespace
886
953
  cache_key = args[0]
887
- path = args[0].split(".")
954
+ input_path = args[0]
955
+ path = input_path.split(".")
888
956
 
889
957
  if isinstance(
890
958
  self.environment.config.import_resolver, FileSystemImportResolver
@@ -904,7 +972,9 @@ class ParseToObjects(Transformer):
904
972
 
905
973
  # we don't iterate past the max parse depth
906
974
  if len(key_path) > MAX_PARSE_DEPTH:
907
- return ImportStatement(alias=alias, path=Path(target))
975
+ return ImportStatement(
976
+ alias=alias, input_path=input_path, path=Path(target)
977
+ )
908
978
 
909
979
  if token_lookup in self.tokens:
910
980
  raw_tokens = self.tokens[token_lookup]
@@ -948,7 +1018,7 @@ class ParseToObjects(Transformer):
948
1018
  ) from e
949
1019
 
950
1020
  parsed_path = Path(args[0])
951
- imps = ImportStatement(alias=alias, path=parsed_path)
1021
+ imps = ImportStatement(alias=alias, input_path=input_path, path=parsed_path)
952
1022
 
953
1023
  self.environment.add_import(
954
1024
  alias, new_env, Import(alias=alias, path=parsed_path)
@@ -1075,7 +1145,7 @@ class ParseToObjects(Transformer):
1075
1145
  if not select_items:
1076
1146
  raise ValueError("Malformed select, missing select items")
1077
1147
 
1078
- return SelectStatement.from_inputs(
1148
+ base = SelectStatement.from_inputs(
1079
1149
  environment=self.environment,
1080
1150
  selection=select_items,
1081
1151
  order_by=order_by,
@@ -1084,6 +1154,7 @@ class ParseToObjects(Transformer):
1084
1154
  limit=limit,
1085
1155
  meta=Metadata(line_number=meta.line),
1086
1156
  )
1157
+ return base
1087
1158
 
1088
1159
  @v_args(meta=True)
1089
1160
  def address(self, meta: Meta, args):
@@ -1514,6 +1585,10 @@ class ParseToObjects(Transformer):
1514
1585
  def fdate_add(self, meta, args):
1515
1586
  return self.function_factory.create_function(args, FunctionType.DATE_ADD, meta)
1516
1587
 
1588
+ @v_args(meta=True)
1589
+ def fdate_sub(self, meta, args):
1590
+ return self.function_factory.create_function(args, FunctionType.DATE_SUB, meta)
1591
+
1517
1592
  @v_args(meta=True)
1518
1593
  def fdate_diff(self, meta, args):
1519
1594
  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