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

trilogy/dialect/base.py CHANGED
@@ -263,7 +263,11 @@ class BaseDialect:
263
263
  ) -> str:
264
264
  result = None
265
265
  if c.pseudonyms:
266
- for candidate in [c] + list(c.pseudonyms.values()):
266
+ candidates = [y for y in [cte.get_concept(x) for x in c.pseudonyms] if y]
267
+ logger.debug(
268
+ f"{LOGGER_PREFIX} [{c.address}] pseudonym candidates are {[x.address for x in candidates]}"
269
+ )
270
+ for candidate in [c] + candidates:
267
271
  try:
268
272
  logger.debug(
269
273
  f"{LOGGER_PREFIX} [{c.address}] Attempting rendering w/ candidate {candidate.address}"
@@ -603,6 +607,7 @@ class BaseDialect:
603
607
  else:
604
608
  having = having + x if having else x
605
609
 
610
+ logger.info(f"{len(final_joins)} joins for cte {cte.name}")
606
611
  return CompiledCTE(
607
612
  name=cte.name,
608
613
  statement=self.SQL_TEMPLATE.render(
trilogy/dialect/common.py CHANGED
@@ -68,39 +68,18 @@ def render_join(
68
68
  if unnest_mode == UnnestMode.CROSS_JOIN_ALIAS:
69
69
  return f"CROSS JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
70
70
  return f"FULL JOIN {render_unnest(unnest_mode, quote_character, join.concept_to_unnest, render_func, cte)}"
71
- left_name = join.left_name
71
+ # left_name = join.left_name
72
72
  right_name = join.right_name
73
73
  right_base = join.right_ref
74
- base_joinkeys = [
75
- null_wrapper(
76
- render_join_concept(
77
- left_name,
78
- quote_character,
79
- join.left_cte,
80
- key.concept,
81
- render_expr_func,
82
- join.inlined_ctes,
83
- ),
84
- render_join_concept(
85
- right_name,
86
- quote_character,
87
- join.right_cte,
88
- key.concept,
89
- render_expr_func,
90
- join.inlined_ctes,
91
- ),
92
- modifiers=key.concept.modifiers or [],
93
- )
94
- for key in join.joinkeys
95
- ]
74
+ base_joinkeys = []
96
75
  if join.joinkey_pairs:
97
76
  base_joinkeys.extend(
98
77
  [
99
78
  null_wrapper(
100
79
  render_join_concept(
101
- left_name,
80
+ join.get_name(pair.cte),
102
81
  quote_character,
103
- join.left_cte,
82
+ pair.cte,
104
83
  pair.left,
105
84
  render_expr_func,
106
85
  join.inlined_ctes,
trilogy/executor.py CHANGED
@@ -334,7 +334,9 @@ class Executor(object):
334
334
  text(command),
335
335
  )
336
336
 
337
- def execute_text(self, command: str) -> List[CursorResult]:
337
+ def execute_text(
338
+ self, command: str, non_interactive: bool = False
339
+ ) -> List[CursorResult]:
338
340
  """Run a preql text command"""
339
341
  output = []
340
342
  # connection = self.engine.connect()
@@ -351,11 +353,18 @@ class Executor(object):
351
353
  )
352
354
  )
353
355
  continue
356
+ if non_interactive:
357
+ if not isinstance(
358
+ statement, (ProcessedCopyStatement, ProcessedQueryPersist)
359
+ ):
360
+ continue
354
361
  output.append(self.execute_query(statement))
355
362
  return output
356
363
 
357
- def execute_file(self, file: str | Path) -> List[CursorResult]:
364
+ def execute_file(
365
+ self, file: str | Path, non_interactive: bool = False
366
+ ) -> List[CursorResult]:
358
367
  file = Path(file)
359
368
  with open(file, "r") as f:
360
369
  command = f.read()
361
- return self.execute_text(command)
370
+ return self.execute_text(command, non_interactive=non_interactive)
trilogy/parsing/common.py CHANGED
@@ -292,21 +292,19 @@ def arbitrary_to_concept(
292
292
 
293
293
  if isinstance(parent, AggregateWrapper):
294
294
  if not name:
295
- name = (
296
- f"_agg_{parent.function.operator.value}_{string_to_hash(str(parent))}"
297
- )
295
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_agg_{parent.function.operator.value}_{string_to_hash(str(parent))}"
298
296
  return agg_wrapper_to_concept(parent, namespace, name, metadata, purpose)
299
297
  elif isinstance(parent, WindowItem):
300
298
  if not name:
301
- name = f"_window_{parent.type.value}_{string_to_hash(str(parent))}"
299
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_window_{parent.type.value}_{string_to_hash(str(parent))}"
302
300
  return window_item_to_concept(parent, name, namespace, purpose, metadata)
303
301
  elif isinstance(parent, FilterItem):
304
302
  if not name:
305
- name = f"_filter_{parent.content.name}_{string_to_hash(str(parent))}"
303
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_filter_{parent.content.name}_{string_to_hash(str(parent))}"
306
304
  return filter_item_to_concept(parent, name, namespace, purpose, metadata)
307
305
  elif isinstance(parent, Function):
308
306
  if not name:
309
- name = f"_func_{parent.operator.value}_{string_to_hash(str(parent))}"
307
+ name = f"{VIRTUAL_CONCEPT_PREFIX}_func_{parent.operator.value}_{string_to_hash(str(parent))}"
310
308
  return function_to_concept(parent, name, namespace)
311
309
  elif isinstance(parent, ListWrapper):
312
310
  if not name:
@@ -254,8 +254,9 @@ class ParseToObjects(Transformer):
254
254
  def IDENTIFIER(self, args) -> str:
255
255
  return args.value
256
256
 
257
- def concept_lit(self, args) -> Concept:
258
- return self.environment.concepts.__getitem__(args[0])
257
+ @v_args(meta=True)
258
+ def concept_lit(self, meta: Meta, args) -> Concept:
259
+ return self.environment.concepts.__getitem__(args[0], meta.line)
259
260
 
260
261
  def ADDRESS(self, args) -> Address:
261
262
  return Address(location=args.value, quoted=False)
trilogy/parsing/render.py CHANGED
@@ -17,6 +17,7 @@ from trilogy.core.models import (
17
17
  SelectItem,
18
18
  WhereClause,
19
19
  Conditional,
20
+ SubselectComparison,
20
21
  Comparison,
21
22
  Environment,
22
23
  ConceptDeclarationStatement,
@@ -25,6 +26,7 @@ from trilogy.core.models import (
25
26
  WindowItem,
26
27
  FilterItem,
27
28
  ColumnAssignment,
29
+ RawColumnExpr,
28
30
  CaseElse,
29
31
  CaseWhen,
30
32
  ImportStatement,
@@ -50,13 +52,16 @@ from collections import defaultdict
50
52
 
51
53
 
52
54
  QUERY_TEMPLATE = Template(
53
- """SELECT{%- for select in select_columns %}
54
- {{ select }},{% endfor %}{% if where %}
55
- WHERE
56
- {{ where }}{% endif %}{%- if order_by %}
55
+ """{% if where %}WHERE
56
+ {{ where }}
57
+ {% endif %}SELECT{%- for select in select_columns %}
58
+ {{ select }},{% endfor %}{% if having %}
59
+ HAVING
60
+ {{ having }}
61
+ {% endif %}{%- if order_by %}
57
62
  ORDER BY{% for order in order_by %}
58
- {{ order }}{% if not loop.last %},{% endif %}
59
- {% endfor %}{% endif %}{%- if limit is not none %}
63
+ {{ order }}{% if not loop.last %},{% endif %}{% endfor %}
64
+ {% endif %}{%- if limit is not none %}
60
65
  LIMIT {{ limit }}{% endif %};"""
61
66
  )
62
67
 
@@ -133,14 +138,14 @@ class Renderer:
133
138
 
134
139
  @to_string.register
135
140
  def _(self, arg: Datasource):
136
- assignments = ",\n\t".join([self.to_string(x) for x in arg.columns])
141
+ assignments = ",\n ".join([self.to_string(x) for x in arg.columns])
137
142
  base = f"""datasource {arg.name} (
138
143
  {assignments}
139
- )
140
- {self.to_string(arg.grain)}
144
+ )
145
+ {self.to_string(arg.grain)}
141
146
  {self.to_string(arg.address)}"""
142
147
  if arg.where:
143
- base += f"\n{self.to_string(arg.where)}"
148
+ base += f"\nwhere {self.to_string(arg.where)}"
144
149
  base += ";"
145
150
  return base
146
151
 
@@ -209,7 +214,13 @@ class Renderer:
209
214
 
210
215
  @to_string.register
211
216
  def _(self, arg: "ColumnAssignment"):
212
- return f"{arg.alias}: {self.to_string(arg.concept)}"
217
+ if isinstance(arg.alias, str):
218
+ return f"{arg.alias}: {self.to_string(arg.concept)}"
219
+ return f"{self.to_string(arg.alias)}: {self.to_string(arg.concept)}"
220
+
221
+ @to_string.register
222
+ def _(self, arg: "RawColumnExpr"):
223
+ return f"raw('''{arg.text}''')"
213
224
 
214
225
  @to_string.register
215
226
  def _(self, arg: "ConceptDeclarationStatement"):
@@ -266,6 +277,7 @@ class Renderer:
266
277
  return QUERY_TEMPLATE.render(
267
278
  select_columns=[self.to_string(c) for c in arg.selection],
268
279
  where=self.to_string(arg.where_clause) if arg.where_clause else None,
280
+ having=self.to_string(arg.having_clause) if arg.having_clause else None,
269
281
  order_by=(
270
282
  [self.to_string(c) for c in arg.order_by.items]
271
283
  if arg.order_by
@@ -301,16 +313,23 @@ class Renderer:
301
313
 
302
314
  @to_string.register
303
315
  def _(self, arg: OrderBy):
304
- return ",\t".join([self.to_string(c) for c in arg.items])
316
+ return ",\n".join([self.to_string(c) for c in arg.items])
305
317
 
306
318
  @to_string.register
307
319
  def _(self, arg: "WhereClause"):
308
- return f"{self.to_string(arg.conditional)}"
320
+ base = f"{self.to_string(arg.conditional)}"
321
+ if base[0] == "(" and base[-1] == ")":
322
+ return base[1:-1]
323
+ return base
309
324
 
310
325
  @to_string.register
311
326
  def _(self, arg: "Conditional"):
312
327
  return f"({self.to_string(arg.left)} {arg.operator.value} {self.to_string(arg.right)})"
313
328
 
329
+ @to_string.register
330
+ def _(self, arg: "SubselectComparison"):
331
+ return f"{self.to_string(arg.left)} {arg.operator.value} {self.to_string(arg.right)}"
332
+
314
333
  @to_string.register
315
334
  def _(self, arg: "Comparison"):
316
335
  return f"{self.to_string(arg.left)} {arg.operator.value} {self.to_string(arg.right)}"
@@ -319,10 +338,12 @@ class Renderer:
319
338
  def _(self, arg: "WindowItem"):
320
339
  over = ",".join(self.to_string(c) for c in arg.over)
321
340
  order = ",".join(self.to_string(c) for c in arg.order_by)
322
- if over:
341
+ if over and order:
323
342
  return (
324
- f"{arg.type.value} {self.to_string(arg.content)} by {order} OVER {over}"
343
+ f"{arg.type.value} {self.to_string(arg.content)} by {order} over {over}"
325
344
  )
345
+ elif over:
346
+ return f"{arg.type.value} {self.to_string(arg.content)} over {over}"
326
347
  return f"{arg.type.value} {self.to_string(arg.content)} by {order}"
327
348
 
328
349
  @to_string.register
@@ -343,13 +364,15 @@ class Renderer:
343
364
 
344
365
  @to_string.register
345
366
  def _(self, arg: "ConceptTransform"):
346
- return f"{self.to_string(arg.function)}->{arg.output.name}"
367
+ return f"{self.to_string(arg.function)} -> {arg.output.name}"
347
368
 
348
369
  @to_string.register
349
370
  def _(self, arg: "Function"):
350
371
  inputs = ",".join(self.to_string(c) for c in arg.arguments)
351
372
  if arg.operator == FunctionType.CONSTANT:
352
373
  return f"{inputs}"
374
+ if arg.operator == FunctionType.CAST:
375
+ return f"CAST({self.to_string(arg.arguments[0])} AS {self.to_string(arg.arguments[1])})"
353
376
  if arg.operator == FunctionType.INDEX_ACCESS:
354
377
  return f"{self.to_string(arg.arguments[0])}[{self.to_string(arg.arguments[1])}]"
355
378
  return f"{arg.operator.value}({inputs})"
@@ -361,7 +384,8 @@ class Renderer:
361
384
  @to_string.register
362
385
  def _(self, arg: AggregateWrapper):
363
386
  if arg.by:
364
- return f"{self.to_string(arg.function)} by {self.to_string(arg.by)}"
387
+ by = ", ".join([self.to_string(x) for x in arg.by])
388
+ return f"{self.to_string(arg.function)} by {by}"
365
389
  return f"{self.to_string(arg.function)}"
366
390
 
367
391
  @to_string.register
@@ -289,8 +289,8 @@
289
289
 
290
290
  // base language constructs
291
291
  concept_lit: IDENTIFIER
292
- IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.\-]*/
293
- QUOTED_ADDRESS: /`[a-zA-Z\_][a-zA-Z0-9\_\-\.\-\*\:]*`/
292
+ IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.]*/
293
+ QUOTED_ADDRESS: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:]*`/
294
294
  ADDRESS: IDENTIFIER
295
295
 
296
296
  MULTILINE_STRING: /\'{3}(.*?)\'{3}/s