Repository: ibis-project/ibis

Issue:
bug: case vs if/else yield different results w/ trino in unexpected way
### What happened?

I think you can see why in the SQL outputs, but I would expect the following 3 to be functionally equivalent: 

```python
[ins] In [1]: # imports # <1>
         ...: import os # <1>
         ...: import ibis # <1>
         ...: from dotenv import load_dotenv # <1>
         ...:
         ...: # configure Ibis # <2>
         ...: ibis.options.interactive = True # <2>
         ...:
         ...: # load env variables # <3>
         ...: load_dotenv() # <3>
         ...: user = os.getenv("USERNAME") # <3>
         ...: password = os.getenv("PASSWORD") # <3>
         ...:
         ...: # variables # <4>
         ...: host = os.getenv("HOSTNAME") # <4>
         ...: port = os.getenv("PORTNUMBER") # <4>
         ...: catalog = "sample" # <4>
         ...: schema = "demo" # <4>
         ...:
         ...: # connect to database # <5>
         ...: con = ibis.trino.connect( # <5>
         ...:     user=user, password=password, host=host, port=port, database=catalog, schema=schema # <5>
         ...: ) # <5>
         ...: con
         ...:
Out[1]: <ibis.backends.trino.Backend at 0x107c00ad0>

[nav] In [2]: astronauts = con.table("astronauts") # <1>
         ...: missions = con.table("missions") # <1>

[ins] In [3]: sql = f"""
         ...: SELECT
         ...:   name,
         ...:   count() AS nbr_of_missions,
         ...:   IF(count() > 1, 'Veteran', 'Rookie') AS nbr_of_mission_qualification
         ...: FROM
         ...:   sample.demo.astronauts
         ...: GROUP BY
         ...:   name;
         ...: """.strip().strip(";")
         ...: print(sql)
SELECT
  name,
  count() AS nbr_of_missions,
  IF(count() > 1, 'Veteran', 'Rookie') AS nbr_of_mission_qualification
FROM
  sample.demo.astronauts
GROUP BY
  name

[ins] In [4]: con.sql(sql)
Out[4]:
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ name                    ┃ nbr_of_missions ┃ nbr_of_mission_qualification ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ string                  │ int64           │ string                       │
├─────────────────────────┼─────────────────┼──────────────────────────────┤
│ Glenn, John H., Jr.     │               2 │ Veteran                      │
│ Carpenter, M. Scott     │               1 │ Rookie                       │
│ Nikolayev, Andriyan     │               2 │ Veteran                      │
│ Popovich, Pavel         │               2 │ Veteran                      │
│ Schirra, Walter M., Jr. │               3 │ Veteran                      │
│ Cooper, L. Gordon, Jr.  │               2 │ Veteran                      │
│ Bykovsky, Valery        │               3 │ Veteran                      │
│ Komarov, Vladimir       │               2 │ Veteran                      │
│ Yegorov, Boris          │               1 │ Rookie                       │
│ Belyayev, Pavel         │               1 │ Rookie                       │
│ …                       │               … │ …                            │
└─────────────────────────┴─────────────────┴──────────────────────────────┘

[ins] In [5]: astronauts.group_by("name").agg(
         ...:     nbr_of_missions=ibis._.count(),
         ...:     nbr_of_mission_qualification=ibis.case()
         ...:     .when(ibis._.count() > 1, "Veteran")
         ...:     .else_("Rookie")
         ...:     .end(),
         ...: )
Out[5]:
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ name                   ┃ nbr_of_missions ┃ nbr_of_mission_qualification ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ string                 │ int64           │ string                       │
├────────────────────────┼─────────────────┼──────────────────────────────┤
│ Gagarin, Yuri          │               1 │ Veteran                      │
│ Titov, Gherman         │               1 │ Veteran                      │
│ Tereshkova, Valentina  │               1 │ Veteran                      │
│ Feoktistov, Konstantin │               1 │ Veteran                      │
│ Leonov, Aleksei        │               2 │ Veteran                      │
│ Grissom, Virgil I.     │               1 │ Veteran                      │
│ McDivitt, James A.     │               2 │ Veteran                      │
│ White, Edward H., II   │               1 │ Veteran                      │
│ Borman, Frank          │               2 │ Veteran                      │
│ Lovell, James A., Jr.  │               4 │ Veteran                      │
│ …                      │               … │ …                            │
└────────────────────────┴─────────────────┴──────────────────────────────┘

[nav] In [6]: print(ibis.to_sql(astronauts.group_by("name").agg(
         ...:     nbr_of_missions=ibis._.count(),
         ...:     nbr_of_mission_qualification=ibis.case()
         ...:     .when(ibis._.count() > 1, "Veteran")
         ...:     .else_("Rookie")
         ...:     .end(),
         ...: )))
SELECT
  t0.name,
  COUNT(*) AS nbr_of_missions,
  CASE WHEN TRUE THEN 'Veteran' ELSE 'Rookie' END AS nbr_of_mission_qualification
FROM "sample".demo.astronauts AS t0
GROUP BY
  1

[ins] In [7]: astronauts.group_by("name").agg(
         ...:     nbr_of_missions=ibis._.count(),
         ...:     nbr_of_mission_qualification=(ibis._.count() > 1).ifelse("Veteran", "Rookie"),
         ...: )
Out[7]:
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ name                   ┃ nbr_of_missions ┃ nbr_of_mission_qualification ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ string                 │ int64           │ string                       │
├────────────────────────┼─────────────────┼──────────────────────────────┤
│ Gagarin, Yuri          │               1 │ Rookie                       │
│ Titov, Gherman         │               1 │ Rookie                       │
│ Tereshkova, Valentina  │               1 │ Rookie                       │
│ Feoktistov, Konstantin │               1 │ Rookie                       │
│ Leonov, Aleksei        │               2 │ Veteran                      │
│ Grissom, Virgil I.     │               1 │ Rookie                       │
│ McDivitt, James A.     │               2 │ Veteran                      │
│ White, Edward H., II   │               1 │ Rookie                       │
│ Borman, Frank          │               2 │ Veteran                      │
│ Lovell, James A., Jr.  │               4 │ Veteran                      │
│ …                      │               … │ …                            │
└────────────────────────┴─────────────────┴──────────────────────────────┘

[ins] In [8]: print(ibis.to_sql(astronauts.group_by("name").agg(
         ...:     nbr_of_missions=ibis._.count(),
         ...:     nbr_of_mission_qualification=(ibis._.count() > 1).ifelse("Veteran", "Rookie"),
         ...: )))
SELECT
  t0.name,
  COUNT(*) AS nbr_of_missions,
  IF(COUNT(*) > 1, 'Veteran', 'Rookie') AS nbr_of_mission_qualification
FROM "sample".demo.astronauts AS t0
GROUP BY
  1
```

### What version of ibis are you using?

master

### What backend(s) are you using, if any?

trino/starburst

### Relevant log output

```sh
see above
```


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
How are you determining whether the different queries match?

Remember that the row order of identical queries **without** an `ORDER BY` clause is not required to be the same for two different runs of the query.

Did you check that the rows differ when sorted by `name`?
I see now that the second case is generating a `TRUE` constant. It would help if the example were a bit smaller.

This is a "deferred doesn't work everywhere" issue:

```
In [6]: ibis.case().when(ibis._.count() > 1, "1").end()
Out[6]: SearchedCase(Cast(None, string)): SearchedCase(cases=[True], results=['1'], default=Cast(None, to=string))
```

Note the `cases=[True]`.

In this case it should error until we can make it work everywhere, so I'd say it's a bug.

Resolving patch:
diff --git a/ibis/expr/operations/generic.py b/ibis/expr/operations/generic.py
--- a/ibis/expr/operations/generic.py
+++ b/ibis/expr/operations/generic.py
@@ -2,7 +2,7 @@
 
 import abc
 import itertools
-from typing import Any, Optional, Union
+from typing import Annotated, Any, Optional, Union
 from typing import Literal as LiteralType
 
 from public import public
@@ -14,7 +14,9 @@
 import ibis.expr.rules as rlz
 from ibis.common.annotations import attribute
 from ibis.common.grounds import Singleton
+from ibis.common.patterns import InstanceOf  # noqa: TCH001
 from ibis.common.typing import VarTuple  # noqa: TCH001
+from ibis.expr.deferred import Deferred  # noqa: TCH001
 from ibis.expr.operations.core import Named, Scalar, Unary, Value
 from ibis.expr.operations.relations import Relation  # noqa: TCH001
 
@@ -193,7 +195,7 @@ class Least(Value):
 
 @public
 class Literal(Scalar[T]):
-    value: Any
+    value: Annotated[Any, ~InstanceOf(Deferred)]
     dtype: T
 
     shape = ds.scalar


Test patch:
diff --git a/ibis/tests/test_strategies.py b/ibis/tests/test_strategies.py
--- a/ibis/tests/test_strategies.py
+++ b/ibis/tests/test_strategies.py
@@ -3,11 +3,14 @@
 import hypothesis as h
 import hypothesis.strategies as st
 import numpy as np
+import pytest
 
+import ibis
 import ibis.expr.datatypes as dt
 import ibis.expr.schema as sch
 import ibis.expr.types as ir
 import ibis.tests.strategies as its
+from ibis.common.annotations import ValidationError
 
 
 @h.given(its.null_dtype)
@@ -169,4 +172,10 @@ def test_memtable(memtable):
     assert isinstance(memtable.schema(), sch.Schema)
 
 
+@h.given(its.all_dtypes())
+def test_deferred_literal(dtype):
+    with pytest.raises(ValidationError):
+        ibis.literal(ibis._.a, type=dtype)
+
+
 # TODO(kszucs): we enforce field name uniqueness in the schema, but we don't for Struct datatype