diff --git a/CHANGELOG.md b/CHANGELOG.md index 31b7a9d9b04b1ef04b5d04ac5565de1aa9bfd204..081ca7101bd9da734ee75044045ff4fd6f1a136c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `least()` functions on `MagicDotPath` - Add `greatest()` functions on `MagicDotPath` ## [1.10.0-pre-release] - 2023-15-12 diff --git a/docs/doc/examples/functions.md b/docs/doc/examples/functions.md index fa96ecfb1633973667c02c0a57ff22cda20a3bc7..bda866de5090970e6f8aa58d3876257783a0335a 100644 --- a/docs/doc/examples/functions.md +++ b/docs/doc/examples/functions.md @@ -27,6 +27,7 @@ removing trailing zeroes. - **trunc(v)**: Truncates to integer (towards zero). - **trunc(v, s)**: Truncates v to s decimal places. - **greatest**: Returns the largest of the values. +- **least**: Returns the smallest of the values. ## Trigonometric functions diff --git a/py_linq_sql/__init__.py b/py_linq_sql/__init__.py index 025411c260e5ca43309b78965ac3cb12435618d5..ffd64aa4eebf8440a43eb9fbd1b698a88d6eb4ca 100644 --- a/py_linq_sql/__init__.py +++ b/py_linq_sql/__init__.py @@ -70,6 +70,7 @@ from .utils.functions.magic_dp_maths_functions import ( # noqa: F401 gcd, greatest, lcm, + least, ln, log, log10, diff --git a/py_linq_sql/utils/classes/op_and_func_of_mdp.py b/py_linq_sql/utils/classes/op_and_func_of_mdp.py index a40a8b3e3598eb1c78b4885db2996834bec4ac79..1212c7132f4db6a6a4a795516808bdabc6b52516 100644 --- a/py_linq_sql/utils/classes/op_and_func_of_mdp.py +++ b/py_linq_sql/utils/classes/op_and_func_of_mdp.py @@ -69,6 +69,7 @@ class MathFunctType(_MathFunction, Enum): TRIM_SCALE = _MathFunction(python=None, psql="trim_scale", name="trimScale") TRUNC = _MathFunction(python="trunc", psql="trunc", name="trunc") GREATEST = _MathFunction(python="max", psql="greatest", name="greatest") + LEAST = _MathFunction(python="min", psql="least", name="least") class OperatorType(_Operator, Enum): @@ -177,6 +178,7 @@ def col_name_math(name_op: DotMap, operator: _OPERATORTYPE) -> str: MathFunctType.ROUND, MathFunctType.TRUNC, MathFunctType.GREATEST, + MathFunctType.LEAST, ]: return f"{operator.name}_{name_op.op1}_{name_op.op2}" @@ -323,7 +325,7 @@ def json_path_math(path_op: DotMap, operator: _OPERATORTYPE) -> str: if operator in [MathFunctType.LOG]: return f"{operator.psql}({path_op.op2}, CAST({path_op.op1} AS decimal))" - if operator in [MathFunctType.GREATEST]: + if operator in [MathFunctType.GREATEST, MathFunctType.LEAST]: operand_1 = f"CAST({path_op.op1} AS decimal)" operand_2 = f"CAST({path_op.op2} AS decimal)" return f"{operator.psql}({operand_1}, {operand_2})" diff --git a/py_linq_sql/utils/functions/magic_dp_maths_functions.py b/py_linq_sql/utils/functions/magic_dp_maths_functions.py index c5a36ced4e17f4aa826ae1a2a2fc55cc229acef0..a10648f8eac92767562e1b3c5b8cdc197fa575e7 100644 --- a/py_linq_sql/utils/functions/magic_dp_maths_functions.py +++ b/py_linq_sql/utils/functions/magic_dp_maths_functions.py @@ -461,3 +461,37 @@ def greatest( other, MathFunctType.GREATEST, ) + + +# TODO: add support for more than 2 operands +# TODO: add support for only one operand +def least( + mdp: BaseMagicDotPath, other: _NUMBER_TYPE | BaseMagicDotPath +) -> MagicDotPathWithOp: + """ + Greatest function for a MagicDotPath. + + This is equivalent to the `min` function in python. + + From psql docs: The LEAST function select the smallest value from a list of any + number of expressions. The expressions must all be convertible to a common data + type, which will be the type of the result + + See: + https://www.postgresql.org/docs/current/functions-conditional.html#FUNCTIONS-GREATEST-LEAST + + Args: + mdp: MagicDotPath on which we apply the least. + other: An other element for + the comparison. + + Returns: + MagicDotPathWithOp with 2 operand and on the correct operator. + + Raises: + TypeOperatorError: Indirect raise by `BaseMagicDotPath._get_number_operator` + """ + return mdp._get_number_operator( # pylint: disable=protected-access + other, + MathFunctType.LEAST, + ) diff --git a/tests/operators/test_more_math_op.py b/tests/operators/test_more_math_op.py index 7ed969754163903ade6ad286b57fa9ac8427b3e0..6c1c0e74b301caa1e315b57ff79def1a1ebb4f37 100644 --- a/tests/operators/test_more_math_op.py +++ b/tests/operators/test_more_math_op.py @@ -30,6 +30,7 @@ from py_linq_sql import floor as floor_sql from py_linq_sql import gcd as gcd_sql from py_linq_sql import greatest as greatest_sql from py_linq_sql import lcm as lcm_sql +from py_linq_sql import least as least_sql from py_linq_sql import ln as ln_sql from py_linq_sql import log as log_sql from py_linq_sql import log10 as log10_sql @@ -791,3 +792,76 @@ def test_math_function_greatest( x.data.mass = input_mass assert_that(val).is_equal_to(input_lambda(max, x)) + + +@pytest.mark.parametrize( + "input_mass, input_lambda", + [ + param( + 23, + lambda least, x: least(x.data.mass, 51), + id="least_left_positive", + ), + param( + 51, + lambda least, x: least(x.data.mass, 23), + id="least_right_positive", + ), + param( + -123, + lambda least, x: least(x.data.mass, -18), + id="least_left_negative", + ), + param( + -18, + lambda least, x: least(x.data.mass, -123), + id="least_right_negative", + ), + param( + -16, + lambda least, x: least(x.data.mass, 18), + id="least_left_negpos", + ), + param( + 16, + lambda least, x: least(x.data.mass, -18), + id="least_right_negpos", + ), + param(0, lambda least, x: least(x.data.mass, 43), id="least_left_zero"), + param(43, lambda least, x: least(x.data.mass, 0), id="least_right_zero"), + param(17, lambda least, x: least(x.data.mass, 17), id="least_left_equal"), + param( + 0, lambda least, x: least(x.data.mass, -43), id="least_left_zero_right_neg" + ), + param( + -43, lambda least, x: least(x.data.mass, 0), id="least_right_zero_left_neg" + ), + param( + 15, + lambda least, x: least(x.data.mass, x.data.mass), + id="least_mass_mass", + ), + ], +) +def test_math_function_least( + db_connection_with_only_schema_for_modif, + table_objects, + input_mass, + input_lambda, +): + insert_value( + db_connection_with_only_schema_for_modif, {"name": "test", "mass": input_mass} + ) + + fquery_least = partial(input_lambda, least_sql) + + val = ( + SQLEnumerable(db_connection_with_only_schema_for_modif, table_objects) + .select(fquery_least) + .execute() + ).to_list()[0][0] + + x = DotMap() + x.data.mass = input_mass + + assert_that(val).is_equal_to(input_lambda(min, x)) diff --git a/tests/operators/test_name_w_op.py b/tests/operators/test_name_w_op.py index edb1c8c787aebe7bca3d9049fa743e8ac5ddf786..c2b10712d56d5f848fd7c3d07e84a43dd6812813 100644 --- a/tests/operators/test_name_w_op.py +++ b/tests/operators/test_name_w_op.py @@ -69,6 +69,12 @@ def test_col_name_hyperB(input_operator, input_expected): "greatest_mass_number", id="greatest_col_name", ), + param( + MathFunctType.LEAST, + DotMap(op1="mass", op2="number"), + "least_mass_number", + id="least_col_name", + ), param( MathFunctType.LCM, DotMap(op1="mass", op2="number"),