pytrilogy 0.0.2.37__tar.gz → 0.0.2.39__tar.gz

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.

Files changed (110) hide show
  1. {pytrilogy-0.0.2.37/pytrilogy.egg-info → pytrilogy-0.0.2.39}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/__init__.py +1 -1
  4. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/models.py +8 -5
  5. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/executor.py +30 -7
  6. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/parse_engine.py +44 -12
  7. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/render.py +3 -1
  8. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/trilogy.lark +3 -3
  9. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/LICENSE.md +0 -0
  10. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/README.md +0 -0
  11. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/pyproject.toml +0 -0
  12. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/pytrilogy.egg-info/SOURCES.txt +0 -0
  13. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/pytrilogy.egg-info/dependency_links.txt +0 -0
  14. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/pytrilogy.egg-info/entry_points.txt +0 -0
  15. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/pytrilogy.egg-info/requires.txt +0 -0
  16. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/pytrilogy.egg-info/top_level.txt +0 -0
  17. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/setup.cfg +0 -0
  18. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/setup.py +0 -0
  19. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_datatypes.py +0 -0
  20. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_declarations.py +0 -0
  21. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_derived_concepts.py +0 -0
  22. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_discovery_nodes.py +0 -0
  23. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_enums.py +0 -0
  24. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_environment.py +0 -0
  25. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_executor.py +0 -0
  26. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_functions.py +0 -0
  27. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_imports.py +0 -0
  28. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_metadata.py +0 -0
  29. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_models.py +0 -0
  30. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_multi_join_assignments.py +0 -0
  31. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_parse_engine.py +0 -0
  32. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_parsing.py +0 -0
  33. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_partial_handling.py +0 -0
  34. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_query_processing.py +0 -0
  35. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_select.py +0 -0
  36. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_show.py +0 -0
  37. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_statements.py +0 -0
  38. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_undefined_concept.py +0 -0
  39. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/tests/test_where_clause.py +0 -0
  40. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/compiler.py +0 -0
  41. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/constants.py +0 -0
  42. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/__init__.py +0 -0
  43. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/constants.py +0 -0
  44. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/enums.py +0 -0
  45. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/env_processor.py +0 -0
  46. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/environment_helpers.py +0 -0
  47. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/ergonomics.py +0 -0
  48. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/exceptions.py +0 -0
  49. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/functions.py +0 -0
  50. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/graph_models.py +0 -0
  51. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/internal.py +0 -0
  52. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/optimization.py +0 -0
  53. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/optimizations/__init__.py +0 -0
  54. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/optimizations/base_optimization.py +0 -0
  55. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/optimizations/inline_constant.py +0 -0
  56. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/optimizations/inline_datasource.py +0 -0
  57. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  58. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/__init__.py +0 -0
  59. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  60. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/graph_utils.py +0 -0
  61. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/__init__.py +0 -0
  62. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  63. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/common.py +0 -0
  64. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  65. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/group_node.py +0 -0
  66. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  67. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  68. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  69. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  70. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  71. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/select_node.py +0 -0
  72. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  73. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/node_generators/window_node.py +0 -0
  74. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/__init__.py +0 -0
  75. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/base_node.py +0 -0
  76. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/filter_node.py +0 -0
  77. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/group_node.py +0 -0
  78. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/merge_node.py +0 -0
  79. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  80. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  81. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/nodes/window_node.py +0 -0
  82. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/processing/utility.py +0 -0
  83. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/core/query_processor.py +0 -0
  84. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/__init__.py +0 -0
  85. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/base.py +0 -0
  86. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/bigquery.py +0 -0
  87. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/common.py +0 -0
  88. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/config.py +0 -0
  89. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/duckdb.py +0 -0
  90. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/enums.py +0 -0
  91. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/postgres.py +0 -0
  92. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/presto.py +0 -0
  93. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/snowflake.py +0 -0
  94. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/dialect/sql_server.py +0 -0
  95. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/engine.py +0 -0
  96. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/hooks/__init__.py +0 -0
  97. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/hooks/base_hook.py +0 -0
  98. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/hooks/graph_hook.py +0 -0
  99. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/hooks/query_debugger.py +0 -0
  100. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/metadata/__init__.py +0 -0
  101. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parser.py +0 -0
  102. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/__init__.py +0 -0
  103. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/common.py +0 -0
  104. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/config.py +0 -0
  105. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/exceptions.py +0 -0
  106. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/parsing/helpers.py +0 -0
  107. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/py.typed +0 -0
  108. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/scripts/__init__.py +0 -0
  109. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/scripts/trilogy.py +0 -0
  110. {pytrilogy-0.0.2.37 → pytrilogy-0.0.2.39}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.37
3
+ Version: 0.0.2.39
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.37
3
+ Version: 0.0.2.39
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.37"
7
+ __version__ = "0.0.2.39"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -2085,14 +2085,16 @@ class DatasourceMetadata(BaseModel):
2085
2085
 
2086
2086
 
2087
2087
  class MergeStatementV2(HasUUID, Namespaced, BaseModel):
2088
- source: Concept
2089
- target: Concept
2088
+ sources: list[Concept]
2089
+ targets: dict[str, Concept]
2090
+ source_wildcard: str | None = None
2091
+ target_wildcard: str | None = None
2090
2092
  modifiers: List[Modifier] = Field(default_factory=list)
2091
2093
 
2092
2094
  def with_namespace(self, namespace: str) -> "MergeStatementV2":
2093
2095
  new = MergeStatementV2(
2094
- source=self.source.with_namespace(namespace),
2095
- target=self.target.with_namespace(namespace),
2096
+ sources=[x.with_namespace(namespace) for x in self.sources],
2097
+ targets={k: v.with_namespace(namespace) for k, v in self.targets.items()},
2096
2098
  modifiers=self.modifiers,
2097
2099
  )
2098
2100
  return new
@@ -3383,7 +3385,7 @@ class Environment(BaseModel):
3383
3385
 
3384
3386
  def __init__(self, **data):
3385
3387
  super().__init__(**data)
3386
- self.concepts["_env_working_path"] = Concept(
3388
+ concept = Concept(
3387
3389
  name="_env_working_path",
3388
3390
  namespace=self.namespace,
3389
3391
  lineage=Function(
@@ -3395,6 +3397,7 @@ class Environment(BaseModel):
3395
3397
  datatype=DataType.STRING,
3396
3398
  purpose=Purpose.CONSTANT,
3397
3399
  )
3400
+ self.add_concept(concept)
3398
3401
 
3399
3402
  @classmethod
3400
3403
  def from_file(cls, path: str | Path) -> "Environment":
@@ -1,4 +1,4 @@
1
- from typing import List, Optional, Any, Generator
1
+ from typing import List, Optional, Any, Generator, Protocol
2
2
  from functools import singledispatchmethod
3
3
  from sqlalchemy import text
4
4
  from sqlalchemy.engine import Engine, CursorResult
@@ -36,6 +36,15 @@ from pathlib import Path
36
36
  from dataclasses import dataclass
37
37
 
38
38
 
39
+ class ResultProtocol(Protocol):
40
+ values: List[Any]
41
+ columns: List[str]
42
+
43
+ def fetchall(self) -> List[Any]: ...
44
+
45
+ def keys(self) -> List[str]: ...
46
+
47
+
39
48
  @dataclass
40
49
  class MockResult:
41
50
  values: list[Any]
@@ -201,15 +210,16 @@ class Executor(object):
201
210
 
202
211
  @execute_query.register
203
212
  def _(self, query: MergeStatementV2) -> CursorResult:
213
+ for concept in query.sources:
214
+ self.environment.merge_concept(
215
+ concept, query.targets[concept.address], modifiers=query.modifiers
216
+ )
204
217
 
205
- self.environment.merge_concept(
206
- query.source, query.target, modifiers=query.modifiers
207
- )
208
218
  return MockResult(
209
219
  [
210
220
  {
211
- "source": query.source.address,
212
- "target": query.target.address,
221
+ "sources": ",".join([x.address for x in query.sources]),
222
+ "targets": ",".join([x.address for _, x in query.targets.items()]),
213
223
  }
214
224
  ],
215
225
  ["source", "target"],
@@ -309,7 +319,20 @@ class Executor(object):
309
319
  output.append(compiled_sql)
310
320
  return output
311
321
 
312
- def parse_file(self, file: str | Path, persist: bool = False) -> Generator[
322
+ def parse_file(
323
+ self, file: str | Path, persist: bool = False
324
+ ) -> list[
325
+ ProcessedQuery
326
+ | ProcessedQueryPersist
327
+ | ProcessedShowStatement
328
+ | ProcessedRawSQLStatement
329
+ | ProcessedCopyStatement,
330
+ ]:
331
+ return list(self.parse_file_generator(file, persist=persist))
332
+
333
+ def parse_file_generator(
334
+ self, file: str | Path, persist: bool = False
335
+ ) -> Generator[
313
336
  ProcessedQuery
314
337
  | ProcessedQueryPersist
315
338
  | ProcessedShowStatement
@@ -302,6 +302,9 @@ class ParseToObjects(Transformer):
302
302
  def IDENTIFIER(self, args) -> str:
303
303
  return args.value
304
304
 
305
+ def WILDCARD_IDENTIFIER(self, args) -> str:
306
+ return args.value
307
+
305
308
  def QUOTED_IDENTIFIER(self, args) -> str:
306
309
  return args.value[1:-1]
307
310
 
@@ -786,19 +789,47 @@ class ParseToObjects(Transformer):
786
789
  return [x for x in args]
787
790
 
788
791
  @v_args(meta=True)
789
- def merge_statement_v2(self, meta: Meta, args) -> MergeStatementV2:
792
+ def merge_statement(self, meta: Meta, args) -> MergeStatementV2:
790
793
  modifiers = []
791
- concepts = []
794
+ cargs: list[str] = []
795
+ source_wildcard = None
796
+ target_wildcard = None
792
797
  for arg in args:
793
798
  if isinstance(arg, Modifier):
794
799
  modifiers.append(arg)
795
800
  else:
796
- concepts.append(self.environment.concepts[arg])
801
+ cargs.append(arg)
802
+ source, target = cargs
803
+ if source.endswith(".*"):
804
+ if not target.endswith(".*"):
805
+ raise ValueError("Invalid merge, source is wildcard, target is not")
806
+ source_wildcard = source[:-2]
807
+ target_wildcard = target[:-2]
808
+ sources = [
809
+ v
810
+ for k, v in self.environment.concepts.items()
811
+ if v.namespace == source_wildcard
812
+ ]
813
+ targets = {}
814
+ for x in sources:
815
+ target = target_wildcard + "." + x.name
816
+ if target in self.environment.concepts:
817
+ targets[x.address] = self.environment.concepts[target]
818
+ sources = [x for x in sources if x.address in targets]
819
+ else:
820
+ sources = [self.environment.concepts[source]]
821
+ targets = {sources[0].address: self.environment.concepts[target]}
797
822
  new = MergeStatementV2(
798
- source=concepts[0], target=concepts[1], modifiers=modifiers
823
+ sources=sources,
824
+ targets=targets,
825
+ modifiers=modifiers,
826
+ source_wildcard=source_wildcard,
827
+ target_wildcard=target_wildcard,
799
828
  )
800
-
801
- self.environment.merge_concept(new.source, new.target, modifiers)
829
+ for source_c in new.sources:
830
+ self.environment.merge_concept(
831
+ source_c, targets[source_c.address], modifiers
832
+ )
802
833
 
803
834
  return new
804
835
 
@@ -1745,7 +1776,7 @@ class ParseToObjects(Transformer):
1745
1776
  arguments=args,
1746
1777
  output_datatype=output_datatype,
1747
1778
  output_purpose=function_args_to_output_purpose(args),
1748
- # valid_inputs={DataType.DATE, DataType.TIMESTAMP, DataType.DATETIME},
1779
+ valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1749
1780
  arg_count=2,
1750
1781
  )
1751
1782
 
@@ -1758,7 +1789,7 @@ class ParseToObjects(Transformer):
1758
1789
  arguments=args,
1759
1790
  output_datatype=output_datatype,
1760
1791
  output_purpose=function_args_to_output_purpose(args),
1761
- # valid_inputs={DataType.DATE, DataType.TIMESTAMP, DataType.DATETIME},
1792
+ valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1762
1793
  arg_count=2,
1763
1794
  )
1764
1795
 
@@ -1771,20 +1802,21 @@ class ParseToObjects(Transformer):
1771
1802
  arguments=args,
1772
1803
  output_datatype=output_datatype,
1773
1804
  output_purpose=function_args_to_output_purpose(args),
1774
- # valid_inputs={DataType.DATE, DataType.TIMESTAMP, DataType.DATETIME},
1805
+ valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1775
1806
  arg_count=2,
1776
1807
  )
1777
1808
 
1778
1809
  @v_args(meta=True)
1779
1810
  def fdiv(self, meta: Meta, args):
1780
1811
  args = process_function_args(args, meta=meta, environment=self.environment)
1781
- output_datatype = merge_datatypes([arg_to_datatype(x) for x in args])
1812
+ # 2024-11-18 - this is a bit of a hack, but division always returns a float
1813
+ # output_datatype = merge_datatypes([arg_to_datatype(x) for x in args])
1782
1814
  return Function(
1783
1815
  operator=FunctionType.DIVIDE,
1784
1816
  arguments=args,
1785
- output_datatype=output_datatype,
1817
+ output_datatype=DataType.FLOAT, # division always returns a float
1786
1818
  output_purpose=function_args_to_output_purpose(args),
1787
- # valid_inputs={DataType.DATE, DataType.TIMESTAMP, DataType.DATETIME},
1819
+ valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
1788
1820
  arg_count=2,
1789
1821
  )
1790
1822
 
@@ -432,7 +432,9 @@ class Renderer:
432
432
 
433
433
  @to_string.register
434
434
  def _(self, arg: MergeStatementV2):
435
- return f"MERGE {self.to_string(arg.source)} into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{self.to_string(arg.target)};"
435
+ if len(arg.sources) == 1:
436
+ return f"MERGE {self.to_string(arg.sources[0])} into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{self.to_string(arg.targets[arg.sources[0].address])};"
437
+ return f"MERGE {arg.source_wildcard}.* into {''.join([self.to_string(modifier) for modifier in arg.modifiers])}{arg.target_wildcard}.*;"
436
438
 
437
439
  @to_string.register
438
440
  def _(self, arg: Modifier):
@@ -9,7 +9,7 @@
9
9
  | rowset_derivation_statement
10
10
  | import_statement
11
11
  | copy_statement
12
- | merge_statement_v2
12
+ | merge_statement
13
13
  | rawsql_statement
14
14
 
15
15
  _TERMINATOR: ";"i /\s*/
@@ -74,8 +74,7 @@
74
74
 
75
75
  align_clause: align_item ("AND"i align_item)* "AND"i?
76
76
 
77
- // merge_v2
78
- merge_statement_v2: "merge"i IDENTIFIER "into"i SHORTHAND_MODIFIER? IDENTIFIER
77
+ merge_statement: "merge"i WILDCARD_IDENTIFIER "into"i SHORTHAND_MODIFIER? WILDCARD_IDENTIFIER
79
78
 
80
79
  // raw sql statement
81
80
  rawsql_statement: "raw_sql"i "(" MULTILINE_STRING ")"
@@ -292,6 +291,7 @@
292
291
  // base language constructs
293
292
  concept_lit: IDENTIFIER
294
293
  IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.]*/
294
+ WILDCARD_IDENTIFIER: /[a-zA-Z\_][a-zA-Z0-9\_\-\.\*]*/
295
295
  QUOTED_IDENTIFIER: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:\s]*`/
296
296
  QUOTED_ADDRESS: /`[a-zA-Z\_][a-zA-Z0-9\_\.\-\*\:]*`/
297
297
  ADDRESS: IDENTIFIER
File without changes
File without changes
File without changes
File without changes