pytrilogy 0.0.3.81__py3-none-any.whl → 0.0.3.83__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.81
3
+ Version: 0.0.3.83
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.81.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=TMYRdrnDsNt6dIWHpcXgKPBKzjBkbMC5oJTgTrClxz0,303
1
+ pytrilogy-0.0.3.83.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=Rh-SluRXHl9PmO1Tte7AvBGZUzGc0qDJwk8oJPx3KtU,303
3
3
  trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  trilogy/constants.py,sha256=eKb_EJvSqjN9tGbdVEViwdtwwh8fZ3-jpOEDqL71y70,1691
5
5
  trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
@@ -11,12 +11,12 @@ trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
11
11
  trilogy/authoring/__init__.py,sha256=e74k-Jep4DLYPQU_2m0aVsYlw5HKTOucAKtdTbd6f2g,2595
12
12
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
14
- trilogy/core/enums.py,sha256=kuwTeEur5TZ07DX8nO3fSiRQ32bLZ5jRK-vIjHbBUnc,8118
14
+ trilogy/core/enums.py,sha256=gIbFV6HT93wUWgfOm6lQeMS_sQcvHc4IK-vuQpwyepY,8158
15
15
  trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0,3170
16
16
  trilogy/core/environment_helpers.py,sha256=VvPIiFemqaLLpIpLIqprfu63K7muZ1YzNg7UZIUph8w,8267
17
17
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
18
  trilogy/core/exceptions.py,sha256=jYEduuMehcMkmCpf-OC_taELPZm7qNfeSNzIWkDYScs,707
19
- trilogy/core/functions.py,sha256=9Q-c7UGxOR0SVwzcHt9mHX_aVroW9jEWwJr7BtHigw8,31766
19
+ trilogy/core/functions.py,sha256=hIvMYbSSsVDsXfHtZ7pwGc2CFIYqsVuYt9GOfAw3BRk,32013
20
20
  trilogy/core/graph_models.py,sha256=BYhJzHKSgnZHVLJs1CfsgrxTPHqKqPNeA64RlozGY0A,3498
21
21
  trilogy/core/internal.py,sha256=wFx4e1I0mtx159YFShSXeUBSQ82NINtAbOI-92RX4i8,2151
22
22
  trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
@@ -35,16 +35,17 @@ trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnb
35
35
  trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
36
36
  trilogy/core/optimizations/predicate_pushdown.py,sha256=g4AYE8Aw_iMlAh68TjNXGP754NTurrDduFECkUjoBnc,9399
37
37
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- trilogy/core/processing/concept_strategies_v3.py,sha256=3Hy8Lz5NOJt8B3cGv_B0LuOYrlcxM_WiPdsFaFWaMjE,23212
38
+ trilogy/core/processing/concept_strategies_v3.py,sha256=Izo7yfR6sGHTaD17lN7ZzGYCtXA5AkXAmIp_OBjHH58,23161
39
39
  trilogy/core/processing/discovery_loop.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- trilogy/core/processing/discovery_node_factory.py,sha256=I3JJxoF-u8OVvqXXAOhvMg2h-KdpHQwg6EpCeQtxGCI,15123
41
- trilogy/core/processing/discovery_utility.py,sha256=3xdd1ypKappSDm0SJs7WtW5YegL80SlYhDQlkNePp4E,4549
42
- trilogy/core/processing/discovery_validation.py,sha256=fGWJmKpgEd1f4RkK-fYOBUT1cwsJnahwXFAdRlou7MI,5365
40
+ trilogy/core/processing/discovery_node_factory.py,sha256=5QVYUsci_h6iYWhS0GCoDow2tSAipiBW1OyTRX-g_L8,15581
41
+ trilogy/core/processing/discovery_utility.py,sha256=eY4n7_r6_R-cx_Sm8FiouMXh78v2iO2SGhi0aI5jvDg,4549
42
+ trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
43
43
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
44
- trilogy/core/processing/utility.py,sha256=xsuVMRFi2nY2So0yohhweI2D92wZVsTpHezS0giM4ck,22583
45
- trilogy/core/processing/node_generators/__init__.py,sha256=w8TQQgNhyAra6JQHdg1_Ags4BGyxjXYruu6UeC5yOkI,873
44
+ trilogy/core/processing/utility.py,sha256=VjKhDFeItB642riF8KHwyk4YEc58nVDiPLIydwdLPcQ,22716
45
+ trilogy/core/processing/node_generators/__init__.py,sha256=iVJ-crowPxYeut-hFjyEjfibKIDq7PfB4LEuDAUCjGY,943
46
46
  trilogy/core/processing/node_generators/basic_node.py,sha256=TLZCv4WS196a-0g5xgKuJGthnGP8Ugm46iz85_3NIY4,5626
47
47
  trilogy/core/processing/node_generators/common.py,sha256=PdysdroW9DUADP7f5Wv_GKPUyCTROZV1g3L45fawxi8,9443
48
+ trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
48
49
  trilogy/core/processing/node_generators/filter_node.py,sha256=oRRq2-T3ufgn4D23uQsc58f20eFk-djs4QI3WKA75K8,10908
49
50
  trilogy/core/processing/node_generators/group_node.py,sha256=1QJhRxsTklJ5xq8wHlAURZaN9gL9FPpeCa1OJ7IwXnY,6769
50
51
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
@@ -76,7 +77,7 @@ trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
76
77
  trilogy/core/statements/common.py,sha256=KxEmz2ySySyZ6CTPzn0fJl5NX2KOk1RPyuUSwWhnK1g,759
77
78
  trilogy/core/statements/execute.py,sha256=pfr1CZ_Cx1qQ-7LDyRI0JUfvtxBr_GGv-VeqiAjr43g,1406
78
79
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
- trilogy/dialect/base.py,sha256=0nVDvsnDS8iMTIlCUsBs_ymoZMW6uh04U4MMZxOuHUw,47428
80
+ trilogy/dialect/base.py,sha256=Bj3Wun81NAtfpjAyJGHDPSogPaTjBO1Bnmm9JxfpyfM,47533
80
81
  trilogy/dialect/bigquery.py,sha256=8xhEu0z_lKANjbvzvBbC7CeKrJf1iP8YyrHqNale-ug,4351
81
82
  trilogy/dialect/common.py,sha256=tSthIZOXXRPQ4KeMKnDDsH7KlTmf2EVqigVtLyoc4zc,6071
82
83
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
@@ -97,9 +98,9 @@ trilogy/parsing/common.py,sha256=yV1AckK0h8u1OFeGQBTMu-wuW5m63c5CcZuPicsTH_w,306
97
98
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
98
99
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
99
100
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
100
- trilogy/parsing/parse_engine.py,sha256=ll3uBwpJ2iAxT-KbbXy7OuCqUvKfLPzB1gHr8gApv_s,78591
101
+ trilogy/parsing/parse_engine.py,sha256=MXG5nzQWd1tVaJddF_NDst96N_ayALQ0EB3JMh8kWaY,78776
101
102
  trilogy/parsing/render.py,sha256=HSNntD82GiiwHT-TWPLXAaIMWLYVV5B5zQEsgwrHIBE,19605
102
- trilogy/parsing/trilogy.lark,sha256=Uhy6A00-GsC-L8fRCN0JZC6fL87ujOOd9_4LbQWApY4,15248
103
+ trilogy/parsing/trilogy.lark,sha256=B7onP5W7ZIW_uKObJ5rL1sQCpnWiMeZ2azQ9j2U2ssE,15368
103
104
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
105
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
105
106
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -110,8 +111,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
110
111
  trilogy/std/net.preql,sha256=7l7MqIjs6TDCpO6dBAoNJU81Ex255jZRK36kBgE1GDs,158
111
112
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
112
113
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
113
- pytrilogy-0.0.3.81.dist-info/METADATA,sha256=0A4NkUsyAunmuRlrFbsAPi8sksv9Yf0l4jAFh6Q_NeU,9589
114
- pytrilogy-0.0.3.81.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
115
- pytrilogy-0.0.3.81.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
116
- pytrilogy-0.0.3.81.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
117
- pytrilogy-0.0.3.81.dist-info/RECORD,,
114
+ pytrilogy-0.0.3.83.dist-info/METADATA,sha256=5Wl6yOEMzIqdUAUuP6OinBib-2jtSmhieDJoDRHCDiA,9589
115
+ pytrilogy-0.0.3.83.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
116
+ pytrilogy-0.0.3.83.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
117
+ pytrilogy-0.0.3.83.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
118
+ pytrilogy-0.0.3.83.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.81"
7
+ __version__ = "0.0.3.83"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/core/enums.py CHANGED
@@ -154,6 +154,7 @@ class FunctionType(Enum):
154
154
  ARRAY_SUM = "array_sum"
155
155
  ARRAY_SORT = "array_sort"
156
156
  ARRAY_TRANSFORM = "array_transform"
157
+ ARRAY_TO_STRING = "array_to_string"
157
158
 
158
159
  # TEXT AND MAYBE MORE
159
160
  SPLIT = "split"
trilogy/core/functions.py CHANGED
@@ -283,6 +283,15 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
283
283
  output_type_function=get_transform_output_type,
284
284
  arg_count=3,
285
285
  ),
286
+ FunctionType.ARRAY_TO_STRING: FunctionConfig(
287
+ valid_inputs={
288
+ DataType.ARRAY,
289
+ DataType.STRING,
290
+ },
291
+ output_purpose=Purpose.PROPERTY,
292
+ output_type=DataType.STRING,
293
+ arg_count=2,
294
+ ),
286
295
  FunctionType.ARRAY_SUM: FunctionConfig(
287
296
  valid_inputs={
288
297
  DataType.ARRAY,
@@ -63,6 +63,11 @@ def generate_candidates_restrictive(
63
63
  and x.address not in priority_concept.pseudonyms
64
64
  and priority_concept.address not in x.pseudonyms
65
65
  ]
66
+
67
+ # if it's single row, joins are irrelevant. Fetch without keys.
68
+ if priority_concept.granularity == Granularity.SINGLE_ROW:
69
+ return [], conditions
70
+
66
71
  if conditions and priority_concept.derivation in ROOT_DERIVATIONS:
67
72
  logger.info(
68
73
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Injecting additional conditional row arguments as all remaining concepts are roots or constant"
@@ -72,9 +77,6 @@ def generate_candidates_restrictive(
72
77
  unique(list(conditions.row_arguments) + local_candidates, "address"),
73
78
  None,
74
79
  )
75
- # if it's single row, joins are irrelevant. Fetch without keys.
76
- if priority_concept.granularity == Granularity.SINGLE_ROW:
77
- return [], conditions
78
80
 
79
81
  return local_candidates, conditions
80
82
 
@@ -324,7 +326,6 @@ def check_for_early_exit(
324
326
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Not complete (missing {missing}), continuing search"
325
327
  )
326
328
  # if we have attempted on root node, we've tried them all.
327
- # inject in another search with filter concepts
328
329
  if priority_concept.derivation == Derivation.ROOT:
329
330
  logger.info(
330
331
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Breaking as attempted root with no results"
@@ -364,7 +365,7 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
364
365
  for x in context.stack
365
366
  }
366
367
  logger.info(
367
- f"Condition {context.conditions} not required, parents included filtering! {parent_map }"
368
+ f"Condition {context.conditions} not required, parents included filtering! {parent_map}"
368
369
  )
369
370
  if len(context.stack) == 1:
370
371
  output: StrategyNode = context.stack[0]
@@ -12,6 +12,7 @@ from trilogy.core.models.build_environment import BuildEnvironment
12
12
  from trilogy.core.processing.discovery_utility import LOGGER_PREFIX, depth_to_prefix
13
13
  from trilogy.core.processing.node_generators import (
14
14
  gen_basic_node,
15
+ gen_constant_node,
15
16
  gen_filter_node,
16
17
  gen_group_node,
17
18
  gen_group_to_node,
@@ -252,6 +253,21 @@ def _generate_basic_node(ctx: NodeGenerationContext) -> StrategyNode | None:
252
253
  )
253
254
 
254
255
 
256
+ def _generate_constant_node(ctx: NodeGenerationContext) -> StrategyNode | None:
257
+ ctx.log_generation("constant")
258
+ return gen_constant_node(
259
+ ctx.concept,
260
+ ctx.local_optional,
261
+ history=ctx.history,
262
+ environment=ctx.environment,
263
+ g=ctx.g,
264
+ depth=ctx.next_depth,
265
+ source_concepts=ctx.source_concepts,
266
+ conditions=ctx.conditions,
267
+ accept_partial=ctx.accept_partial,
268
+ )
269
+
270
+
255
271
  class RootNodeHandler:
256
272
  """Handles complex root node generation logic."""
257
273
 
@@ -469,7 +485,7 @@ def generate_node(
469
485
  Derivation.GROUP_TO: lambda: _generate_group_to_node(context),
470
486
  Derivation.BASIC: lambda: _generate_basic_node(context),
471
487
  Derivation.ROOT: lambda: RootNodeHandler(context).generate(),
472
- Derivation.CONSTANT: lambda: RootNodeHandler(context).generate(),
488
+ Derivation.CONSTANT: lambda: _generate_constant_node(context),
473
489
  }
474
490
 
475
491
  handler = derivation_handlers.get(concept.derivation)
@@ -73,11 +73,11 @@ def get_priority_concept(
73
73
  + [c for c in remaining_concept if c.derivation == Derivation.RECURSIVE]
74
74
  + [c for c in remaining_concept if c.derivation == Derivation.BASIC]
75
75
  + [c for c in remaining_concept if c.derivation == Derivation.GROUP_TO]
76
+ + [c for c in remaining_concept if c.derivation == Derivation.CONSTANT]
76
77
  # finally our plain selects
77
78
  + [
78
79
  c for c in remaining_concept if c.derivation == Derivation.ROOT
79
80
  ] # and any non-single row constants
80
- + [c for c in remaining_concept if c.derivation == Derivation.CONSTANT]
81
81
  )
82
82
 
83
83
  priority += [
@@ -105,7 +105,6 @@ def validate_stack(
105
105
 
106
106
  for concept in resolved.output_concepts:
107
107
  if concept.address in resolved.hidden_concepts:
108
-
109
108
  continue
110
109
 
111
110
  validate_concept(
@@ -1,4 +1,5 @@
1
1
  from .basic_node import gen_basic_node
2
+ from .constant_node import gen_constant_node
2
3
  from .filter_node import gen_filter_node
3
4
  from .group_node import gen_group_node
4
5
  from .group_to_node import gen_group_to_node
@@ -26,4 +27,5 @@ __all__ = [
26
27
  "gen_multiselect_node",
27
28
  "gen_synonym_node",
28
29
  "gen_recursive_node",
30
+ "gen_constant_node",
29
31
  ]
@@ -0,0 +1,38 @@
1
+ from typing import List
2
+
3
+ from trilogy.core.models.build import BuildConcept, BuildWhereClause
4
+ from trilogy.core.models.build_environment import BuildEnvironment
5
+ from trilogy.core.processing.nodes import History, StrategyNode
6
+
7
+ LOGGER_PREFIX = "[GEN_CONSTANT_NODE]"
8
+
9
+
10
+ def gen_constant_node(
11
+ concept: BuildConcept,
12
+ local_optional: List[BuildConcept],
13
+ environment: BuildEnvironment,
14
+ g,
15
+ depth: int,
16
+ source_concepts,
17
+ history: History | None = None,
18
+ conditions: BuildWhereClause | None = None,
19
+ accept_partial: bool = False,
20
+ ):
21
+ """our only goal here is to generate a row if conditions exist, or none if they do not"""
22
+
23
+ targets = [concept] + local_optional
24
+ if conditions:
25
+ targets += conditions.row_arguments
26
+ parent_node: StrategyNode | None = source_concepts(
27
+ mandatory_list=targets,
28
+ environment=environment,
29
+ g=g,
30
+ depth=depth + 1,
31
+ history=history,
32
+ conditions=conditions,
33
+ accept_partial=accept_partial,
34
+ )
35
+ if not parent_node:
36
+ return None
37
+ parent_node.set_output_concepts([concept] + local_optional)
38
+ return parent_node
@@ -10,6 +10,7 @@ from trilogy.constants import MagicConstants
10
10
  from trilogy.core.enums import (
11
11
  BooleanOperator,
12
12
  DatePart,
13
+ Derivation,
13
14
  FunctionClass,
14
15
  Granularity,
15
16
  JoinType,
@@ -262,16 +263,19 @@ def calculate_graph_relevance(
262
263
  """
263
264
  relevance = 0
264
265
  for node in g.nodes:
266
+
265
267
  if node not in subset_nodes:
266
268
  continue
267
269
  if not g.nodes[node]["type"] == NodeType.CONCEPT:
268
270
  continue
269
271
  concept = [x for x in concepts if x.address == node].pop()
270
-
272
+ # debug granularity and derivation
271
273
  # a single row concept can always be crossjoined
272
274
  # therefore a graph with only single row concepts is always relevant
273
275
  if concept.granularity == Granularity.SINGLE_ROW:
274
276
  continue
277
+ if concept.derivation == Derivation.CONSTANT:
278
+ continue
275
279
  # if it's an aggregate up to an arbitrary grain, it can be joined in later
276
280
  # and can be ignored in subgraph
277
281
  if concept.purpose == Purpose.METRIC:
@@ -284,7 +288,6 @@ def calculate_graph_relevance(
284
288
  continue
285
289
  # Added 2023-10-18 since we seemed to be strangely dropping things
286
290
  relevance += 1
287
-
288
291
  return relevance
289
292
 
290
293
 
trilogy/dialect/base.py CHANGED
@@ -193,6 +193,9 @@ FUNCTION_MAP = {
193
193
  FunctionType.ARRAY_TRANSFORM: lambda args: (
194
194
  f"array_transform({args[0]}, {args[1]} -> {args[2]})"
195
195
  ),
196
+ FunctionType.ARRAY_TO_STRING: lambda args: (
197
+ f"array_to_string({args[0]}, {args[1]})"
198
+ ),
196
199
  # math
197
200
  FunctionType.ADD: lambda x: " + ".join(x),
198
201
  FunctionType.ABS: lambda x: f"abs({x[0]})",
@@ -2034,6 +2034,12 @@ class ParseToObjects(Transformer):
2034
2034
  args, FunctionType.ARRAY_DISTINCT, meta
2035
2035
  )
2036
2036
 
2037
+ @v_args(meta=True)
2038
+ def farray_to_string(self, meta, args):
2039
+ return self.function_factory.create_function(
2040
+ args, FunctionType.ARRAY_TO_STRING, meta
2041
+ )
2042
+
2037
2043
  @v_args(meta=True)
2038
2044
  def farray_sort(self, meta, args):
2039
2045
  if len(args) == 1:
@@ -300,13 +300,15 @@
300
300
  farray_sum: _ARRAY_SUM expr ")"
301
301
  _ARRAY_DISTINCT.1: "array_distinct("i
302
302
  farray_distinct: _ARRAY_DISTINCT expr ")"
303
+ _ARRAY_TO_STRING.1: "array_to_string("i
304
+ farray_to_string: _ARRAY_TO_STRING expr "," expr ")"
303
305
  _ARRAY_SORT.1: "array_sort("i
304
306
  farray_sort: _ARRAY_SORT expr ("," ordering )? ")"
305
307
  _ARRAY_TRANSFORM.1: "array_transform("i
306
308
  transform_lambda: "@" IDENTIFIER
307
309
  farray_transform: _ARRAY_TRANSFORM expr "," transform_lambda ")"
308
310
 
309
- _array_functions: farray_sum | farray_distinct | farray_sort | farray_transform
311
+ _array_functions: farray_sum | farray_distinct | farray_sort | farray_transform | farray_to_string
310
312
 
311
313
  // special aggregate
312
314
  _GROUP.1: "group("i