diff --git a/pylegend/core/language/operations/collection_operation_expressions.py b/pylegend/core/language/operations/collection_operation_expressions.py index 3e3ba2d2..ff52e74e 100644 --- a/pylegend/core/language/operations/collection_operation_expressions.py +++ b/pylegend/core/language/operations/collection_operation_expressions.py @@ -70,6 +70,7 @@ "PyLegendStrictDateMaxExpression", "PyLegendStrictDateMinExpression", "PyLegendDateMaxExpression", + "PyLegendDateMinExpression", ] @@ -491,3 +492,22 @@ def __init__(self, operand: PyLegendExpressionDateReturn) -> None: operand, PyLegendDateMaxExpression.__to_sql_func ) + + +class PyLegendDateMinExpression(PyLegendUnaryExpression, PyLegendExpressionDateReturn): + + @staticmethod + def __to_sql_func( + expression: Expression, + frame_name_to_base_query_map: PyLegendDict[str, QuerySpecification], + config: FrameToSqlConfig + ) -> Expression: + return MinExpression(value=expression) + + def __init__(self, operand: PyLegendExpressionDateReturn) -> None: + PyLegendExpressionDateReturn.__init__(self) + PyLegendUnaryExpression.__init__( + self, + operand, + PyLegendDateMinExpression.__to_sql_func + ) diff --git a/pylegend/core/language/primitive_collection.py b/pylegend/core/language/primitive_collection.py index 840b8d34..560b6047 100644 --- a/pylegend/core/language/primitive_collection.py +++ b/pylegend/core/language/primitive_collection.py @@ -53,6 +53,7 @@ PyLegendStrictDateMaxExpression, PyLegendStrictDateMinExpression, PyLegendDateMaxExpression, + PyLegendDateMinExpression, ) @@ -271,6 +272,13 @@ def max(self) -> "PyLegendDate": ) return PyLegendDate(PyLegendDateMaxExpression(nested_expr)) # type: ignore + def min(self) -> "PyLegendDate": + nested_expr = ( + convert_literal_to_literal_expression(self.__nested) if isinstance(self.__nested, (date, datetime)) + else self.__nested.value() + ) + return PyLegendDate(PyLegendDateMinExpression(nested_expr)) # type: ignore + class PyLegendDateTimeCollection(PyLegendDateCollection): __nested: PyLegendUnion[datetime, PyLegendDateTime] diff --git a/pylegend/tests/core/tds/legend_api/frames/functions/test_group_by_function.py b/pylegend/tests/core/tds/legend_api/frames/functions/test_group_by_function.py index e56539f0..08d7adb4 100644 --- a/pylegend/tests/core/tds/legend_api/frames/functions/test_group_by_function.py +++ b/pylegend/tests/core/tds/legend_api/frames/functions/test_group_by_function.py @@ -907,6 +907,29 @@ def test_sql_gen_group_by_date_max_agg(self) -> None: "root".col1''' assert frame.to_sql_query(FrameToSqlConfig()) == dedent(expected) + def test_sql_gen_group_by_date_min_agg(self) -> None: + columns = [ + PrimitiveTdsColumn.number_column("col1"), + PrimitiveTdsColumn.date_column("col2") + ] + frame: LegendApiTdsFrame = LegendApiTableSpecInputFrame(['test_schema', 'test_table'], columns) + frame = frame.group_by( + ["col1"], + [AggregateSpecification(lambda x: x["col2"], lambda y: y.min(), "Minimum")] # type: ignore + ) + assert "[" + ", ".join([str(c) for c in frame.columns()]) + "]" == ( + "[TdsColumn(Name: col1, Type: Number), TdsColumn(Name: Minimum, Type: Date)]" + ) + expected = '''\ + SELECT + "root".col1 AS "col1", + MIN("root".col2) AS "Minimum" + FROM + test_schema.test_table AS "root" + GROUP BY + "root".col1''' + assert frame.to_sql_query(FrameToSqlConfig()) == dedent(expected) + def test_e2e_group_by(self, legend_test_server: PyLegendDict[str, PyLegendUnion[int, ]]) -> None: frame: LegendApiTdsFrame = simple_person_service_frame(legend_test_server['engine_port']) frame = frame.group_by( @@ -1570,3 +1593,25 @@ def test_e2e_group_by_date_max_agg(self, legend_test_server: PyLegendDict[str, P {'values': ['Firm X', '2014-12-02T21:00:00.000000000+0000']}]} res = frame.execute_frame_to_string() assert json.loads(res)["result"] == expected + + def test_e2e_group_by_date_min_agg(self, legend_test_server: PyLegendDict[str, PyLegendUnion[int, ]]) -> None: + frame: LegendApiTdsFrame = simple_trade_service_frame(legend_test_server['engine_port']) + frame = frame.group_by( + ["Product/Name"], + [ + AggregateSpecification( + lambda x: x['Settlement Date Time'], + lambda y: y.min(), # type: ignore + 'Min Date Time' + ) + ] + ) + assert "[" + ", ".join([str(c) for c in frame.columns()]) + "]" == \ + "[TdsColumn(Name: Product/Name, Type: String), TdsColumn(Name: Min Date Time, Type: Date)]" + expected = {'columns': ['Product/Name', 'Min Date Time'], + 'rows': [{'values': [None, None]}, + {'values': ['Firm A', '2014-12-02T21:00:00.000000000+0000']}, + {'values': ['Firm C', '2014-12-04T15:22:23.123456789+0000']}, + {'values': ['Firm X', '2014-12-02T21:00:00.000000000+0000']}]} + res = frame.execute_frame_to_string() + assert json.loads(res)["result"] == expected