diff --git a/ibis/expr/api.py b/ibis/expr/api.py index ff0b54fbafe6..b6e79761a382 100644 --- a/ibis/expr/api.py +++ b/ibis/expr/api.py @@ -1838,9 +1838,83 @@ def where(cond, true_expr, false_expr) -> ir.Value: return ifelse(cond, true_expr, false_expr) -coalesce = _deferred(ir.Value.coalesce) -greatest = _deferred(ir.Value.greatest) -least = _deferred(ir.Value.least) +@deferrable +def coalesce(*args: Any) -> ir.Value: + """Return the first non-null value from `args`. + + Parameters + ---------- + args + Arguments from which to choose the first non-null value + + Returns + ------- + Value + Coalesced expression + + Examples + -------- + >>> import ibis + >>> ibis.options.interactive = True + >>> ibis.coalesce(None, 4, 5) + 4 + """ + if not args: + raise ValueError("Must provide at least one argument") + return ops.Coalesce(args).to_expr() + + +@deferrable +def greatest(*args: Any) -> ir.Value: + """Compute the largest value among the supplied arguments. + + Parameters + ---------- + args + Arguments to choose from + + Returns + ------- + Value + Maximum of the passed arguments + + Examples + -------- + >>> import ibis + >>> ibis.options.interactive = True + >>> ibis.greatest(None, 4, 5) + 5 + """ + if not args: + raise ValueError("Must provide at least one argument") + return ops.Greatest(args).to_expr() + + +@deferrable +def least(*args: Any) -> ir.Value: + """Compute the smallest value among the supplied arguments. + + Parameters + ---------- + args + Arguments to choose from + + Returns + ------- + Value + Minimum of the passed arguments + + Examples + -------- + >>> import ibis + >>> ibis.options.interactive = True + >>> ibis.least(None, 4, 5) + 4 + """ + if not args: + raise ValueError("Must provide at least one argument") + return ops.Least(args).to_expr() + aggregate = ir.Table.aggregate cross_join = ir.Table.cross_join diff --git a/ibis/tests/expr/test_sql_builtins.py b/ibis/tests/expr/test_sql_builtins.py index d74d6da882bc..2c0248a68ebb 100644 --- a/ibis/tests/expr/test_sql_builtins.py +++ b/ibis/tests/expr/test_sql_builtins.py @@ -18,6 +18,7 @@ import ibis import ibis.expr.operations as ops import ibis.expr.types as ir +from ibis import _ from ibis.tests.util import assert_equal @@ -206,3 +207,15 @@ def test_floats(sql_table, function): expr = function(5.5, 5) assert isinstance(expr, ir.FloatingScalar) + + +def test_deferred(sql_table, function): + expr = function(None, _.v3, 2) + res = expr.resolve(sql_table) + sol = function(None, sql_table.v3, 2) + assert res.equals(sol) + + +def test_no_arguments_errors(function): + with pytest.raises(ValueError, match="at least one argument"): + function() diff --git a/ibis/tests/expr/test_table.py b/ibis/tests/expr/test_table.py index 6e234adc223a..7e2b7f553564 100644 --- a/ibis/tests/expr/test_table.py +++ b/ibis/tests/expr/test_table.py @@ -1861,7 +1861,7 @@ def test_invalid_deferred(): t = ibis.table(dict(value="int", lagged_value="int"), name="t") with pytest.raises(ValidationError): - ibis.greatest(t.value, ibis._.lagged_value) + ops.Greatest((t.value, ibis._.lagged_value)) @pytest.mark.parametrize("keep", ["last", None])