Repository: ibis-project/ibis

Issue:
bug: map values and concatenation don't work in DuckDB
### What happened?

For merge we get this error:

```python
import ibis
import pyarrow as pa
ibis.options.interactive = True
tab = pa.table({
    "m1": pa.array([{"a": 1, "b": 2}, {"a": 1}, None], 
                   type=pa.map_(pa.utf8(), pa.int64())),
    "m2": pa.array([{"b": 3, "c": 4}, {"b": 1}, {"a": 1}],
                   type=pa.map_(pa.utf8(), pa.int64()))})
t = ibis.memtable(tab)
t.m1 + t.m2
```

```
sqlalchemy.exc.ProgrammingError: (duckdb.InvalidInputException) Invalid Input Error: List of paths must be constant
[SQL: SELECT CAST(map(json_keys(json_merge_patch(to_json(t0.m1), to_json(t0.m2))), json_extract_string(json_merge_patch(to_json(t0.m1), to_json(t0.m2)), json_keys(json_merge_patch(to_json(t0.m1), to_json(t0.m2))))) AS MAP(TEXT, BIGINT)) AS "MapMerge(m1, m2)" 
FROM _ibis_pyarrow_memtable_di3oe2l1xkhgwphe6rc7su3hj AS t0
```

It seems like we should just be able to raise some not implemented error

### What version of ibis are you using?

master

### 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:

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
@@ -268,6 +268,10 @@ def _map_keys(t, op):
 
 
 def _map_values(t, op):
+    if not isinstance(op.arg, ops.Literal):
+        raise UnsupportedOperationError(
+            "Extracting values of non-literal maps is not yet supported by DuckDB"
+        )
     m_json = sa.func.to_json(t.translate(op.arg))
     return sa.cast(
         sa.func.json_extract_string(m_json, sa.func.json_keys(m_json)),
@@ -276,6 +280,10 @@ def _map_values(t, op):
 
 
 def _map_merge(t, op):
+    if not isinstance(op.left, ops.Literal) or not isinstance(op.right, ops.Literal):
+        raise UnsupportedOperationError(
+            "Merging non-literal maps is not yet supported by DuckDB"
+        )
     left = sa.func.to_json(t.translate(op.left))
     right = sa.func.to_json(t.translate(op.right))
     pairs = sa.func.json_merge_patch(left, right)


Test patch:
diff --git a/ibis/backends/tests/test_map.py b/ibis/backends/tests/test_map.py
--- a/ibis/backends/tests/test_map.py
+++ b/ibis/backends/tests/test_map.py
@@ -2,10 +2,12 @@
 
 import numpy as np
 import pandas as pd
+import pandas.testing as tm
 import pytest
 from pytest import param
 
 import ibis
+import ibis.common.exceptions as exc
 import ibis.expr.datatypes as dt
 from ibis.util import guid
 
@@ -30,6 +32,27 @@ def test_map_table(backend):
     assert not table.limit(1).execute().empty
 
 
+@pytest.mark.notimpl(["pandas", "dask"])
+@pytest.mark.notyet(["duckdb"], raises=exc.UnsupportedOperationError)
+def test_column_map_values(backend):
+    table = backend.map
+    result = table.kv.values().name("values").execute().map(sorted)
+    expected = pd.Series([[1, 2, 3], [4, 5, 6]], name="values")
+    tm.assert_series_equal(result, expected)
+
+
+@pytest.mark.notimpl(["pandas", "dask"])
+@pytest.mark.notyet(["duckdb"], raises=exc.UnsupportedOperationError)
+def test_column_map_merge(backend):
+    table = backend.map
+    expr = (table.kv.cast("map<string, int8>") + ibis.map({"d": 1})).name("merged")
+    result = expr.execute()
+    expected = pd.Series(
+        [{'a': 1, 'b': 2, 'c': 3, 'd': 1}, {'d': 1, 'e': 5, 'f': 6}], name="merged"
+    )
+    tm.assert_series_equal(result, expected)
+
+
 def test_literal_map_keys(con):
     mapping = ibis.literal({'1': 'a', '2': 'b'})
     expr = mapping.keys().name('tmp')