Repository: ibis-project/ibis

Issue:
bug: .substitute() doesn't fill params right
### What happened?

It looks like https://github.com/ibis-project/ibis/issues/5522 is fixed (thanks!), but there is another issue in there too 🙃 

```python
m = {
    # Works if there is only one item, but not if there are multiple
    "A": "a",
    "B": "b",
}
# This errors:
ibis.literal("foo").replace("x", "y").substitute(m)
# This works:
# ibis.literal("foo").substitute(m)
```

### What version of ibis are you using?

main

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

duckdb

### Relevant log output

_No response_

### Code of Conduct

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


Discussion:
Filed an upstream issue for at least one underlying issue with the duckdb version of this: https://github.com/duckdb/duckdb/issues/6276

Resolving patch:
diff --git a/ibis/backends/duckdb/registry.py b/ibis/backends/duckdb/registry.py
--- a/ibis/backends/duckdb/registry.py
+++ b/ibis/backends/duckdb/registry.py
@@ -17,6 +17,9 @@
     geospatial_functions,
     reduction,
 )
+from ibis.backends.base.sql.alchemy.registry import (
+    _translate_case as _base_translate_case,
+)
 from ibis.backends.postgres.registry import (
     _array_index,
     _array_slice,
@@ -197,6 +200,26 @@ def _struct_column(t, op):
     )
 
 
+def _simple_case(t, op):
+    return _translate_case(t, op, value=t.translate(op.base))
+
+
+def _searched_case(t, op):
+    return _translate_case(t, op, value=None)
+
+
+def _translate_case(t, op, *, value):
+    return sa.literal_column(
+        str(
+            _base_translate_case(t, op, value=value).compile(
+                dialect=sa.dialects.registry.load("duckdb")(),
+                compile_kwargs=dict(literal_binds=True),
+            )
+        ),
+        type_=t.get_sqla_type(op.output_dtype),
+    )
+
+
 operation_registry.update(
     {
         ops.ArrayColumn: (
@@ -278,6 +301,8 @@ def _struct_column(t, op):
         ops.ArrayStringJoin: fixed_arity(
             lambda sep, arr: sa.func.array_aggr(arr, sa.text("'string_agg'"), sep), 2
         ),
+        ops.SearchedCase: _searched_case,
+        ops.SimpleCase: _simple_case,
         ops.StartsWith: fixed_arity(sa.func.prefix, 2),
         ops.EndsWith: fixed_arity(sa.func.suffix, 2),
     }
diff --git a/ibis/backends/pandas/execution/generic.py b/ibis/backends/pandas/execution/generic.py
--- a/ibis/backends/pandas/execution/generic.py
+++ b/ibis/backends/pandas/execution/generic.py
@@ -1365,7 +1365,11 @@ def wrap_case_result(raw, expr):
             raw_1d, dtype=constants.IBIS_TYPE_TO_PANDAS_TYPE[expr.type()]
         )
     if result.size == 1 and isinstance(expr, ir.Scalar):
-        return result.iloc[0].item()
+        value = result.iloc[0]
+        try:
+            return value.item()
+        except AttributeError:
+            return value
     return result
 
 


Test patch:
diff --git a/ibis/backends/tests/test_string.py b/ibis/backends/tests/test_string.py
--- a/ibis/backends/tests/test_string.py
+++ b/ibis/backends/tests/test_string.py
@@ -829,8 +829,18 @@ def test_array_string_join(con):
     assert con.execute(expr) == expected
 
 
-@pytest.mark.notimpl(["dask", "datafusion", "mssql", "mysql", "pandas", "pyspark"])
+@pytest.mark.notimpl(
+    ["datafusion", "mssql", "mysql", "pyspark"], raises=com.OperationNotDefinedError
+)
 def test_subs_with_re_replace(con):
     expr = ibis.literal("hi").re_replace("i", "a").substitute({"d": "b"}, else_="k")
     result = con.execute(expr)
     assert result == "k"
+
+
+@pytest.mark.notimpl(["pyspark", "datafusion"], raises=com.OperationNotDefinedError)
+def test_multiple_subs(con):
+    m = {"foo": "FOO", "bar": "BAR"}
+    expr = ibis.literal("foo").substitute(m)
+    result = con.execute(expr)
+    assert result == "FOO"