pytrilogy 0.0.3.42__tar.gz → 0.0.3.44__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.
- {pytrilogy-0.0.3.42/pytrilogy.egg-info → pytrilogy-0.0.3.44}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44/pytrilogy.egg-info}/PKG-INFO +1 -1
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/pytrilogy.egg-info/SOURCES.txt +1 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_models.py +0 -3
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_parsing.py +1 -101
- pytrilogy-0.0.3.44/tests/test_parsing_failures.py +143 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/__init__.py +1 -1
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/author.py +12 -14
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/build.py +52 -47
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/common.py +1 -1
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/window_node.py +19 -5
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/bigquery.py +1 -1
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/common.py +40 -25
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/parse_engine.py +106 -19
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/trilogy.lark +44 -34
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/README.md +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/pyproject.toml +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/pytrilogy.egg-info/dependency_links.txt +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/pytrilogy.egg-info/entry_points.txt +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/pytrilogy.egg-info/requires.txt +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/pytrilogy.egg-info/top_level.txt +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/setup.cfg +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/setup.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_datatypes.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_declarations.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_derived_concepts.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_discovery_nodes.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_enums.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_environment.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_executor.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_failure.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_functions.py +1 -1
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_imports.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_metadata.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_multi_join_assignments.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_parse_engine.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_partial_handling.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_query_processing.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_query_render.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_select.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_show.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_statements.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_typing.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_undefined_concept.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_user_functions.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/tests/test_where_clause.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/authoring/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/compiler.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/constants.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/constants.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/enums.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/env_processor.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/environment_helpers.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/ergonomics.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/exceptions.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/functions.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/graph_models.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/internal.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/build_environment.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/core.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/datasource.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/environment.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/models/execute.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/optimization.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/optimizations/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/optimizations/base_optimization.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/optimizations/inline_constant.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/optimizations/inline_datasource.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/concept_strategies_v3.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/graph_utils.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/basic_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/filter_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/group_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/select_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/union_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/base_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/filter_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/group_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/merge_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/union_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/unnest_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/nodes/window_node.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/utility.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/query_processor.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/statements/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/statements/author.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/statements/build.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/statements/common.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/statements/execute.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/base.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/common.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/config.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/dataframe.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/duckdb.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/enums.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/postgres.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/presto.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/snowflake.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/dialect/sql_server.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/engine.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/executor.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/hooks/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/hooks/base_hook.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/hooks/graph_hook.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/hooks/query_debugger.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/metadata/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parser.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/config.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/exceptions.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/helpers.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/parsing/render.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/py.typed +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/render.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/scripts/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/scripts/trilogy.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/std/__init__.py +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/std/date.preql +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/std/display.preql +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/std/geography.preql +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/std/money.preql +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/std/report.preql +0 -0
- {pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/utility.py +0 -0
|
@@ -213,9 +213,6 @@ def test_comparison():
|
|
|
213
213
|
with raises(SyntaxError):
|
|
214
214
|
Comparison(left=1, right="abc", operator=ComparisonOperator.EQ)
|
|
215
215
|
|
|
216
|
-
with raises(SyntaxError):
|
|
217
|
-
Comparison(left=1, right=1, operator=ComparisonOperator.IN)
|
|
218
|
-
|
|
219
216
|
# this should not error
|
|
220
217
|
Comparison(left=1, right=[1, 2, 3], operator=ComparisonOperator.IN)
|
|
221
218
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
from pytest import raises
|
|
2
|
-
|
|
3
1
|
from trilogy import Dialects
|
|
4
|
-
from trilogy.constants import MagicConstants
|
|
2
|
+
from trilogy.constants import MagicConstants
|
|
5
3
|
from trilogy.core.enums import BooleanOperator, ComparisonOperator, Purpose
|
|
6
|
-
from trilogy.core.exceptions import InvalidSyntaxException
|
|
7
4
|
from trilogy.core.functions import argument_to_purpose, function_args_to_output_purpose
|
|
8
5
|
from trilogy.core.models.author import Comparison
|
|
9
6
|
from trilogy.core.models.core import (
|
|
@@ -20,7 +17,6 @@ from trilogy.core.statements.author import SelectStatement, ShowStatement
|
|
|
20
17
|
from trilogy.core.statements.execute import ProcessedQuery
|
|
21
18
|
from trilogy.dialect.base import BaseDialect
|
|
22
19
|
from trilogy.parsing.parse_engine import (
|
|
23
|
-
ParseError,
|
|
24
20
|
arg_to_datatype,
|
|
25
21
|
parse_text,
|
|
26
22
|
)
|
|
@@ -668,99 +664,3 @@ select x % 10 -> x_mod_10;
|
|
|
668
664
|
|
|
669
665
|
"""
|
|
670
666
|
)
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
def test_import_shows_source():
|
|
674
|
-
|
|
675
|
-
env = Environment(
|
|
676
|
-
config=EnvironmentOptions(
|
|
677
|
-
import_resolver=DictImportResolver(
|
|
678
|
-
content={
|
|
679
|
-
"test": """
|
|
680
|
-
import test_dep as test_dep;
|
|
681
|
-
key x int;
|
|
682
|
-
datasource test (
|
|
683
|
-
x: x)
|
|
684
|
-
grain(x)
|
|
685
|
-
query '''
|
|
686
|
-
select 1 as x
|
|
687
|
-
union all
|
|
688
|
-
select 11 as x
|
|
689
|
-
''' TYPO
|
|
690
|
-
""",
|
|
691
|
-
"test_dep": """
|
|
692
|
-
key x int;
|
|
693
|
-
""",
|
|
694
|
-
}
|
|
695
|
-
)
|
|
696
|
-
)
|
|
697
|
-
)
|
|
698
|
-
assert isinstance(env.config.import_resolver, DictImportResolver)
|
|
699
|
-
|
|
700
|
-
with raises(Exception, match="Unable to import 'test', parsing error") as e:
|
|
701
|
-
env.parse(
|
|
702
|
-
"""
|
|
703
|
-
import test;
|
|
704
|
-
|
|
705
|
-
select x % 10 -> x_mod_10;
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
"""
|
|
709
|
-
)
|
|
710
|
-
assert "TYPO" in str(e.value)
|
|
711
|
-
assert 1 == 0
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
def test_concept_shadow_warning():
|
|
715
|
-
x = """
|
|
716
|
-
key scalar int;
|
|
717
|
-
property scalar.int_array list<int>;
|
|
718
|
-
|
|
719
|
-
key split <- unnest(int_array);
|
|
720
|
-
|
|
721
|
-
datasource avalues (
|
|
722
|
-
int_array: int_array,
|
|
723
|
-
scalar: scalar
|
|
724
|
-
)
|
|
725
|
-
grain (scalar)
|
|
726
|
-
query '''(
|
|
727
|
-
select [1,2,3,4] as int_array, 2 as scalar
|
|
728
|
-
union all
|
|
729
|
-
select [5,6,7,8] as int_array, 4 as scalar
|
|
730
|
-
)''';
|
|
731
|
-
|
|
732
|
-
SELECT
|
|
733
|
-
int_array,
|
|
734
|
-
1+2->scalar
|
|
735
|
-
;
|
|
736
|
-
"""
|
|
737
|
-
with raises(ParseError):
|
|
738
|
-
env, parsed = parse_text(
|
|
739
|
-
x, parse_config=Parsing(strict_name_shadow_enforcement=True)
|
|
740
|
-
)
|
|
741
|
-
x = """
|
|
742
|
-
key scalar int;
|
|
743
|
-
property scalar.int_array list<int>;
|
|
744
|
-
|
|
745
|
-
key split <- unnest(int_array);
|
|
746
|
-
|
|
747
|
-
datasource avalues (
|
|
748
|
-
int_array: int_array,
|
|
749
|
-
scalar: scalar
|
|
750
|
-
)
|
|
751
|
-
grain (scalar)
|
|
752
|
-
query '''(
|
|
753
|
-
select [1,2,3,4] as int_array, 2 as scalar
|
|
754
|
-
union all
|
|
755
|
-
select [5,6,7,8] as int_array, 4 as scalar
|
|
756
|
-
)''';
|
|
757
|
-
|
|
758
|
-
SELECT
|
|
759
|
-
int_array,
|
|
760
|
-
sum(scalar)->scalar
|
|
761
|
-
;
|
|
762
|
-
"""
|
|
763
|
-
with raises(InvalidSyntaxException):
|
|
764
|
-
env, parsed = parse_text(
|
|
765
|
-
x, parse_config=Parsing(strict_name_shadow_enforcement=True)
|
|
766
|
-
)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from pytest import raises
|
|
2
|
+
|
|
3
|
+
from trilogy.constants import Parsing
|
|
4
|
+
from trilogy.core.exceptions import InvalidSyntaxException
|
|
5
|
+
from trilogy.core.models.environment import (
|
|
6
|
+
DictImportResolver,
|
|
7
|
+
Environment,
|
|
8
|
+
EnvironmentOptions,
|
|
9
|
+
)
|
|
10
|
+
from trilogy.parsing.parse_engine import (
|
|
11
|
+
ParseError,
|
|
12
|
+
parse_text,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_import_shows_source():
|
|
17
|
+
|
|
18
|
+
env = Environment(
|
|
19
|
+
config=EnvironmentOptions(
|
|
20
|
+
import_resolver=DictImportResolver(
|
|
21
|
+
content={
|
|
22
|
+
"test": """
|
|
23
|
+
import test_dep as test_dep;
|
|
24
|
+
key x int;
|
|
25
|
+
datasource test (
|
|
26
|
+
x: x)
|
|
27
|
+
grain(x)
|
|
28
|
+
query '''
|
|
29
|
+
select 1 as x
|
|
30
|
+
union all
|
|
31
|
+
select 11 as x
|
|
32
|
+
''' TYPO
|
|
33
|
+
""",
|
|
34
|
+
"test_dep": """
|
|
35
|
+
key x int;
|
|
36
|
+
""",
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
assert isinstance(env.config.import_resolver, DictImportResolver)
|
|
42
|
+
|
|
43
|
+
with raises(Exception, match="Unable to import 'test', parsing error") as e:
|
|
44
|
+
env.parse(
|
|
45
|
+
"""
|
|
46
|
+
import test;
|
|
47
|
+
|
|
48
|
+
select x % 10 -> x_mod_10;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
)
|
|
53
|
+
assert "TYPO" in str(e.value)
|
|
54
|
+
assert 1 == 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_concept_shadow_warning():
|
|
58
|
+
x = """
|
|
59
|
+
key scalar int;
|
|
60
|
+
property scalar.int_array list<int>;
|
|
61
|
+
|
|
62
|
+
key split <- unnest(int_array);
|
|
63
|
+
|
|
64
|
+
datasource avalues (
|
|
65
|
+
int_array: int_array,
|
|
66
|
+
scalar: scalar
|
|
67
|
+
)
|
|
68
|
+
grain (scalar)
|
|
69
|
+
query '''(
|
|
70
|
+
select [1,2,3,4] as int_array, 2 as scalar
|
|
71
|
+
union all
|
|
72
|
+
select [5,6,7,8] as int_array, 4 as scalar
|
|
73
|
+
)''';
|
|
74
|
+
|
|
75
|
+
SELECT
|
|
76
|
+
int_array,
|
|
77
|
+
1+2->scalar
|
|
78
|
+
;
|
|
79
|
+
"""
|
|
80
|
+
with raises(ParseError) as e:
|
|
81
|
+
env, parsed = parse_text(
|
|
82
|
+
x, parse_config=Parsing(strict_name_shadow_enforcement=True)
|
|
83
|
+
)
|
|
84
|
+
assert "abc" in str(e)
|
|
85
|
+
x = """
|
|
86
|
+
key scalar int;
|
|
87
|
+
property scalar.int_array list<int>;
|
|
88
|
+
|
|
89
|
+
key split <- unnest(int_array);
|
|
90
|
+
|
|
91
|
+
datasource avalues (
|
|
92
|
+
int_array: int_array,
|
|
93
|
+
scalar: scalar
|
|
94
|
+
)
|
|
95
|
+
grain (scalar)
|
|
96
|
+
query '''(
|
|
97
|
+
select [1,2,3,4] as int_array, 2 as scalar
|
|
98
|
+
union all
|
|
99
|
+
select [5,6,7,8] as int_array, 4 as scalar
|
|
100
|
+
)''';
|
|
101
|
+
|
|
102
|
+
SELECT
|
|
103
|
+
int_array,
|
|
104
|
+
sum(scalar)->scalar
|
|
105
|
+
;
|
|
106
|
+
"""
|
|
107
|
+
with raises(InvalidSyntaxException):
|
|
108
|
+
env, parsed = parse_text(
|
|
109
|
+
x, parse_config=Parsing(strict_name_shadow_enforcement=True)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_parsing_bad_order():
|
|
114
|
+
x = """
|
|
115
|
+
key scalar int;
|
|
116
|
+
property scalar.int_array list<int>;
|
|
117
|
+
|
|
118
|
+
key split <- unnest(int_array);
|
|
119
|
+
|
|
120
|
+
datasource avalues (
|
|
121
|
+
int_array: int_array,
|
|
122
|
+
scalar: scalar
|
|
123
|
+
)
|
|
124
|
+
grain (scalar)
|
|
125
|
+
query '''(
|
|
126
|
+
select [1,2,3,4] as int_array, 2 as scalar
|
|
127
|
+
union all
|
|
128
|
+
select [5,6,7,8] as int_array, 4 as scalar
|
|
129
|
+
)''';
|
|
130
|
+
|
|
131
|
+
SELECT
|
|
132
|
+
int_array,
|
|
133
|
+
sum(scalar)->scalar
|
|
134
|
+
order
|
|
135
|
+
int_array asc
|
|
136
|
+
;
|
|
137
|
+
"""
|
|
138
|
+
with raises(InvalidSyntaxException) as e:
|
|
139
|
+
|
|
140
|
+
env, parsed = parse_text(
|
|
141
|
+
x, parse_config=Parsing(strict_name_shadow_enforcement=True)
|
|
142
|
+
)
|
|
143
|
+
assert "^" in str(e)
|
|
@@ -629,18 +629,8 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
|
|
|
629
629
|
)
|
|
630
630
|
elif self.operator in (ComparisonOperator.IN, ComparisonOperator.NOT_IN):
|
|
631
631
|
right_type = arg_to_datatype(self.right)
|
|
632
|
-
if not any(
|
|
633
|
-
[
|
|
634
|
-
isinstance(self.right, ConceptRef),
|
|
635
|
-
right_type in (DataType.LIST,),
|
|
636
|
-
isinstance(right_type, (ListType, ListWrapper, TupleWrapper)),
|
|
637
|
-
]
|
|
638
|
-
):
|
|
639
|
-
raise SyntaxError(
|
|
640
|
-
f"Cannot use {self.operator.value} with non-list, non-tuple, non-concept object {self.right} in {str(self)}"
|
|
641
|
-
)
|
|
642
632
|
|
|
643
|
-
|
|
633
|
+
if isinstance(right_type, ListType) and not is_compatible_datatype(
|
|
644
634
|
arg_to_datatype(self.left), right_type.value_data_type
|
|
645
635
|
):
|
|
646
636
|
raise SyntaxError(
|
|
@@ -1916,7 +1906,7 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
|
|
|
1916
1906
|
|
|
1917
1907
|
|
|
1918
1908
|
class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
|
|
1919
|
-
content:
|
|
1909
|
+
content: Expr
|
|
1920
1910
|
where: "WhereClause"
|
|
1921
1911
|
|
|
1922
1912
|
@field_validator("content", mode="before")
|
|
@@ -1932,13 +1922,21 @@ class FilterItem(DataTyped, Namespaced, ConceptArgs, BaseModel):
|
|
|
1932
1922
|
self, source: Concept, target: Concept, modifiers: List[Modifier]
|
|
1933
1923
|
) -> "FilterItem":
|
|
1934
1924
|
return FilterItem.model_construct(
|
|
1935
|
-
content=
|
|
1925
|
+
content=(
|
|
1926
|
+
self.content.with_merge(source, target, modifiers)
|
|
1927
|
+
if isinstance(self.content, Mergeable)
|
|
1928
|
+
else self.content
|
|
1929
|
+
),
|
|
1936
1930
|
where=self.where.with_merge(source, target, modifiers),
|
|
1937
1931
|
)
|
|
1938
1932
|
|
|
1939
1933
|
def with_namespace(self, namespace: str) -> "FilterItem":
|
|
1940
1934
|
return FilterItem.model_construct(
|
|
1941
|
-
content=
|
|
1935
|
+
content=(
|
|
1936
|
+
self.content.with_namespace(namespace)
|
|
1937
|
+
if isinstance(self.content, Namespaced)
|
|
1938
|
+
else self.content
|
|
1939
|
+
),
|
|
1942
1940
|
where=self.where.with_namespace(namespace),
|
|
1943
1941
|
)
|
|
1944
1942
|
|
|
@@ -1496,22 +1496,43 @@ class Factory:
|
|
|
1496
1496
|
):
|
|
1497
1497
|
return base
|
|
1498
1498
|
|
|
1499
|
+
def instantiate_concept(
|
|
1500
|
+
self,
|
|
1501
|
+
arg: (
|
|
1502
|
+
AggregateWrapper
|
|
1503
|
+
| FunctionCallWrapper
|
|
1504
|
+
| WindowItem
|
|
1505
|
+
| FilterItem
|
|
1506
|
+
| Function
|
|
1507
|
+
| ListWrapper[Any]
|
|
1508
|
+
| MapWrapper[Any, Any]
|
|
1509
|
+
| int
|
|
1510
|
+
| float
|
|
1511
|
+
| str
|
|
1512
|
+
),
|
|
1513
|
+
) -> tuple[Concept, BuildConcept]:
|
|
1514
|
+
from trilogy.parsing.common import arbitrary_to_concept
|
|
1515
|
+
|
|
1516
|
+
new = arbitrary_to_concept(
|
|
1517
|
+
arg,
|
|
1518
|
+
environment=self.environment,
|
|
1519
|
+
)
|
|
1520
|
+
built = self.build(new)
|
|
1521
|
+
self.local_concepts[new.address] = built
|
|
1522
|
+
return new, built
|
|
1523
|
+
|
|
1499
1524
|
@build.register
|
|
1500
1525
|
def _(self, base: None) -> None:
|
|
1501
1526
|
return base
|
|
1502
1527
|
|
|
1503
1528
|
@build.register
|
|
1504
1529
|
def _(self, base: Function) -> BuildFunction | BuildAggregateWrapper:
|
|
1505
|
-
from trilogy.parsing.common import arbitrary_to_concept
|
|
1506
1530
|
|
|
1507
1531
|
raw_args: list[Concept | FuncArgs] = []
|
|
1508
1532
|
for arg in base.arguments:
|
|
1509
1533
|
# to do proper discovery, we need to inject virtual intermediate ocncepts
|
|
1510
1534
|
if isinstance(arg, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1511
|
-
narg =
|
|
1512
|
-
arg,
|
|
1513
|
-
environment=self.environment,
|
|
1514
|
-
)
|
|
1535
|
+
narg, _ = self.instantiate_concept(arg)
|
|
1515
1536
|
raw_args.append(narg)
|
|
1516
1537
|
else:
|
|
1517
1538
|
raw_args.append(arg)
|
|
@@ -1532,24 +1553,16 @@ class Factory:
|
|
|
1532
1553
|
for x in arguments:
|
|
1533
1554
|
if isinstance(x, (ConceptRef, Concept)):
|
|
1534
1555
|
final_args.append(x)
|
|
1535
|
-
elif isinstance(x, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1536
|
-
newx = arbitrary_to_concept(
|
|
1537
|
-
x,
|
|
1538
|
-
environment=self.environment,
|
|
1539
|
-
)
|
|
1540
|
-
final_args.append(newx)
|
|
1541
1556
|
else:
|
|
1542
1557
|
# constants, etc, can be ignored for group
|
|
1543
1558
|
continue
|
|
1544
|
-
|
|
1559
|
+
_, rval = self.instantiate_concept(
|
|
1545
1560
|
AggregateWrapper(
|
|
1546
1561
|
function=group_base.lineage.function,
|
|
1547
1562
|
by=final_args,
|
|
1548
|
-
)
|
|
1549
|
-
environment=self.environment,
|
|
1563
|
+
)
|
|
1550
1564
|
)
|
|
1551
1565
|
|
|
1552
|
-
rval = self.build(group_base)
|
|
1553
1566
|
return BuildFunction.model_construct(
|
|
1554
1567
|
operator=base.operator,
|
|
1555
1568
|
arguments=[rval, *[self.build(c) for c in raw_args[1:]]],
|
|
@@ -1580,20 +1593,13 @@ class Factory:
|
|
|
1580
1593
|
|
|
1581
1594
|
@build.register
|
|
1582
1595
|
def _(self, base: CaseWhen) -> BuildCaseWhen:
|
|
1583
|
-
from trilogy.parsing.common import arbitrary_to_concept
|
|
1584
1596
|
|
|
1585
1597
|
comparison = base.comparison
|
|
1586
1598
|
if isinstance(comparison, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1587
|
-
comparison =
|
|
1588
|
-
comparison,
|
|
1589
|
-
environment=self.environment,
|
|
1590
|
-
)
|
|
1599
|
+
comparison, _ = self.instantiate_concept(comparison)
|
|
1591
1600
|
expr: Concept | FuncArgs = base.expr
|
|
1592
1601
|
if isinstance(expr, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1593
|
-
expr =
|
|
1594
|
-
expr,
|
|
1595
|
-
environment=self.environment,
|
|
1596
|
-
)
|
|
1602
|
+
expr, _ = self.instantiate_concept(expr)
|
|
1597
1603
|
return BuildCaseWhen.model_construct(
|
|
1598
1604
|
comparison=self.build(comparison),
|
|
1599
1605
|
expr=self.build(expr),
|
|
@@ -1615,6 +1621,7 @@ class Factory:
|
|
|
1615
1621
|
else:
|
|
1616
1622
|
build_lineage = None
|
|
1617
1623
|
derivation = Concept.calculate_derivation(build_lineage, base.purpose)
|
|
1624
|
+
|
|
1618
1625
|
granularity = Concept.calculate_granularity(
|
|
1619
1626
|
derivation, final_grain, build_lineage
|
|
1620
1627
|
)
|
|
@@ -1675,16 +1682,12 @@ class Factory:
|
|
|
1675
1682
|
|
|
1676
1683
|
@build.register
|
|
1677
1684
|
def _(self, base: OrderItem) -> BuildOrderItem:
|
|
1678
|
-
from trilogy.parsing.common import arbitrary_to_concept
|
|
1679
1685
|
|
|
1680
1686
|
bexpr: Any
|
|
1681
1687
|
if isinstance(base.expr, (AggregateWrapper, WindowItem, FilterItem)) or (
|
|
1682
1688
|
isinstance(base.expr, Function) and base.expr.operator == FunctionType.GROUP
|
|
1683
1689
|
):
|
|
1684
|
-
bexpr =
|
|
1685
|
-
base.expr,
|
|
1686
|
-
environment=self.environment,
|
|
1687
|
-
)
|
|
1690
|
+
bexpr, _ = self.instantiate_concept(base.expr)
|
|
1688
1691
|
else:
|
|
1689
1692
|
bexpr = base.expr
|
|
1690
1693
|
return BuildOrderItem.model_construct(
|
|
@@ -1707,15 +1710,10 @@ class Factory:
|
|
|
1707
1710
|
|
|
1708
1711
|
@build.register
|
|
1709
1712
|
def _(self, base: WindowItem) -> BuildWindowItem:
|
|
1710
|
-
# to do proper discovery, we need to inject virtual intermediate ocncepts
|
|
1711
|
-
from trilogy.parsing.common import arbitrary_to_concept
|
|
1712
1713
|
|
|
1713
1714
|
content: Concept | FuncArgs = base.content
|
|
1714
1715
|
if isinstance(content, (AggregateWrapper, FilterItem, WindowItem)):
|
|
1715
|
-
content =
|
|
1716
|
-
content,
|
|
1717
|
-
environment=self.environment,
|
|
1718
|
-
)
|
|
1716
|
+
content, _ = self.instantiate_concept(content)
|
|
1719
1717
|
final_by = []
|
|
1720
1718
|
for x in base.order_by:
|
|
1721
1719
|
if (
|
|
@@ -1743,30 +1741,26 @@ class Factory:
|
|
|
1743
1741
|
|
|
1744
1742
|
@build.register
|
|
1745
1743
|
def _(self, base: SubselectComparison) -> BuildSubselectComparison:
|
|
1744
|
+
right: Any = base.right
|
|
1745
|
+
if isinstance(base.right, (AggregateWrapper, WindowItem, FilterItem, Function)):
|
|
1746
|
+
right_c, _ = self.instantiate_concept(base.right)
|
|
1747
|
+
right = right_c
|
|
1746
1748
|
return BuildSubselectComparison.model_construct(
|
|
1747
|
-
left=
|
|
1748
|
-
right=
|
|
1749
|
+
left=self.build(base.left),
|
|
1750
|
+
right=self.build(right),
|
|
1749
1751
|
operator=base.operator,
|
|
1750
1752
|
)
|
|
1751
1753
|
|
|
1752
1754
|
@build.register
|
|
1753
1755
|
def _(self, base: Comparison) -> BuildComparison:
|
|
1754
|
-
from trilogy.parsing.common import arbitrary_to_concept
|
|
1755
1756
|
|
|
1756
1757
|
left = base.left
|
|
1757
1758
|
if isinstance(left, (AggregateWrapper, WindowItem, FilterItem)):
|
|
1758
|
-
left_c =
|
|
1759
|
-
left,
|
|
1760
|
-
environment=self.environment,
|
|
1761
|
-
)
|
|
1759
|
+
left_c, _ = self.instantiate_concept(left)
|
|
1762
1760
|
left = left_c # type: ignore
|
|
1763
1761
|
right = base.right
|
|
1764
1762
|
if isinstance(right, (AggregateWrapper, WindowItem, FilterItem)):
|
|
1765
|
-
right_c =
|
|
1766
|
-
right,
|
|
1767
|
-
environment=self.environment,
|
|
1768
|
-
)
|
|
1769
|
-
|
|
1763
|
+
right_c, _ = self.instantiate_concept(right)
|
|
1770
1764
|
right = right_c # type: ignore
|
|
1771
1765
|
return BuildComparison.model_construct(
|
|
1772
1766
|
left=(self.build(left)),
|
|
@@ -1826,6 +1820,13 @@ class Factory:
|
|
|
1826
1820
|
|
|
1827
1821
|
@build.register
|
|
1828
1822
|
def _(self, base: FilterItem) -> BuildFilterItem:
|
|
1823
|
+
if isinstance(
|
|
1824
|
+
base.content, (Function, AggregateWrapper, WindowItem, FilterItem)
|
|
1825
|
+
):
|
|
1826
|
+
_, built = self.instantiate_concept(base.content)
|
|
1827
|
+
return BuildFilterItem.model_construct(
|
|
1828
|
+
content=built, where=self.build(base.where)
|
|
1829
|
+
)
|
|
1829
1830
|
return BuildFilterItem.model_construct(
|
|
1830
1831
|
content=self.build(base.content), where=self.build(base.where)
|
|
1831
1832
|
)
|
|
@@ -1969,6 +1970,10 @@ class Factory:
|
|
|
1969
1970
|
new.datasources[k] = self.build(d)
|
|
1970
1971
|
for k, a in base.alias_origin_lookup.items():
|
|
1971
1972
|
new.alias_origin_lookup[k] = self.build(a)
|
|
1973
|
+
# add in anything that was built as a side-effect
|
|
1974
|
+
for bk, bv in self.local_concepts.items():
|
|
1975
|
+
if bk not in new.concepts:
|
|
1976
|
+
new.concepts[bk] = bv
|
|
1972
1977
|
new.gen_concept_list_caches()
|
|
1973
1978
|
return new
|
|
1974
1979
|
|
|
@@ -178,7 +178,7 @@ def gen_enrichment_node(
|
|
|
178
178
|
for x in extra_required
|
|
179
179
|
):
|
|
180
180
|
log_lambda(
|
|
181
|
-
f"{str(type(base_node).__name__)} returning property optimized enrichment node"
|
|
181
|
+
f"{str(type(base_node).__name__)} returning property optimized enrichment node for {extra_required[0].keys}"
|
|
182
182
|
)
|
|
183
183
|
return gen_property_enrichment_node(
|
|
184
184
|
base_node,
|
{pytrilogy-0.0.3.42 → pytrilogy-0.0.3.44}/trilogy/core/processing/node_generators/window_node.py
RENAMED
|
@@ -32,7 +32,9 @@ def resolve_window_parent_concepts(
|
|
|
32
32
|
if concept.lineage.order_by:
|
|
33
33
|
for item in concept.lineage.order_by:
|
|
34
34
|
base += item.concept_arguments
|
|
35
|
-
|
|
35
|
+
if concept.grain:
|
|
36
|
+
for gitem in concept.grain.components:
|
|
37
|
+
base.append(environment.concepts[gitem])
|
|
36
38
|
return concept.lineage.content, unique(base, "address")
|
|
37
39
|
|
|
38
40
|
|
|
@@ -61,6 +63,9 @@ def gen_window_node(
|
|
|
61
63
|
# append in keys to get the right grain
|
|
62
64
|
if concept.keys:
|
|
63
65
|
for item in concept.keys:
|
|
66
|
+
logger.info(
|
|
67
|
+
f"{padding(depth)}{LOGGER_PREFIX} appending search for key {item}"
|
|
68
|
+
)
|
|
64
69
|
targets.append(environment.concepts[item])
|
|
65
70
|
additional_outputs = []
|
|
66
71
|
if equivalent_optional:
|
|
@@ -72,10 +77,15 @@ def gen_window_node(
|
|
|
72
77
|
additional_outputs.append(x)
|
|
73
78
|
|
|
74
79
|
grain_equivalents = [
|
|
75
|
-
x
|
|
80
|
+
x
|
|
81
|
+
for x in local_optional
|
|
82
|
+
if x.keys
|
|
83
|
+
and all([key in targets for key in x.keys])
|
|
84
|
+
and x.grain == concept.grain
|
|
76
85
|
]
|
|
77
86
|
|
|
78
87
|
for x in grain_equivalents:
|
|
88
|
+
logger.info("Appending grain equivalent %s", x)
|
|
79
89
|
targets.append(x)
|
|
80
90
|
|
|
81
91
|
# finally, the ones we'll need to enrich
|
|
@@ -123,20 +133,24 @@ def gen_window_node(
|
|
|
123
133
|
)
|
|
124
134
|
_window_node.rebuild_cache()
|
|
125
135
|
_window_node.resolve()
|
|
136
|
+
|
|
126
137
|
window_node = StrategyNode(
|
|
127
138
|
input_concepts=[concept] + additional_outputs + parent_concepts + targets,
|
|
128
139
|
output_concepts=[concept] + additional_outputs + parent_concepts + targets,
|
|
129
140
|
environment=environment,
|
|
130
141
|
parents=[_window_node],
|
|
131
142
|
preexisting_conditions=conditions.conditional if conditions else None,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
grain=BuildGrain.from_concepts(
|
|
144
|
+
concepts=[concept] + additional_outputs + parent_concepts + targets,
|
|
145
|
+
environment=environment,
|
|
146
|
+
),
|
|
135
147
|
)
|
|
136
148
|
if not non_equivalent_optional:
|
|
137
149
|
logger.info(
|
|
138
150
|
f"{padding(depth)}{LOGGER_PREFIX} no optional concepts, returning window node"
|
|
139
151
|
)
|
|
152
|
+
# prune outputs if we don't need join keys
|
|
153
|
+
window_node.set_output_concepts([concept] + additional_outputs + targets)
|
|
140
154
|
return window_node
|
|
141
155
|
|
|
142
156
|
missing_optional = [
|
|
@@ -34,7 +34,7 @@ FUNCTION_MAP = {
|
|
|
34
34
|
|
|
35
35
|
FUNCTION_GRAIN_MATCH_MAP = {
|
|
36
36
|
**FUNCTION_MAP,
|
|
37
|
-
FunctionType.COUNT: lambda args:
|
|
37
|
+
FunctionType.COUNT: lambda args: "1",
|
|
38
38
|
FunctionType.SUM: lambda args: f"{args[0]}",
|
|
39
39
|
FunctionType.AVG: lambda args: f"{args[0]}",
|
|
40
40
|
}
|