Skip to content

Commit

Permalink
fix(duckdb): workaround for duckdb Map NULL bugs (#8649)
Browse files Browse the repository at this point in the history
  • Loading branch information
NickCrews authored Apr 17, 2024
1 parent 9c29f28 commit 75d32e5
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 2 deletions.
6 changes: 6 additions & 0 deletions ibis/backends/duckdb/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ def visit_ArrayZip(self, op, *, arg):
any_arg_null = sg.or_(*(arr.is_(NULL) for arr in arg))
return self.if_(any_arg_null, NULL, zipped_arrays)

def visit_Map(self, op, *, keys, values):
# workaround for https://github.com/ibis-project/ibis/issues/8632
regular = self.f.map(keys, values)
either_null = sg.or_(keys.is_(NULL), values.is_(NULL))
return self.if_(either_null, NULL, regular)

def visit_MapGet(self, op, *, arg, key, default):
return self.f.ifnull(
self.f.list_extract(self.f.element_at(arg, key), 1), default
Expand Down
8 changes: 8 additions & 0 deletions ibis/backends/duckdb/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,11 @@ def test_list_tables_schema_warning_refactor(con):

assert con.list_tables(database="shops") == icecream_table
assert con.list_tables(database=("shops",)) == icecream_table


@pytest.mark.xfail(
raises=(duckdb.InvalidInputException, duckdb.BinderException),
reason="https://github.com/ibis-project/ibis/issues/8632",
)
def test_map_null_workaround(con):
con.sql("SELECT MAP([0, 1, 2], NULL::INT[])").to_pyarrow()
5 changes: 4 additions & 1 deletion ibis/backends/postgres/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,10 @@ def visit_ToJSONArray(self, op, *, arg):
)

def visit_Map(self, op, *, keys, values):
return self.f.map(self.f.array(*keys), self.f.array(*values))
# map(["a", "b"], NULL) results in {"a": NULL, "b": NULL} in regular postgres,
# so we need to modify it to return NULL instead
regular = self.f.map(keys, values)
return self.if_(values.is_(NULL), NULL, regular)

def visit_MapLength(self, op, *, arg):
return self.f.cardinality(self.f.akeys(arg))
Expand Down
24 changes: 23 additions & 1 deletion ibis/backends/tests/test_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import ibis
import ibis.common.exceptions as exc
import ibis.expr.datatypes as dt
from ibis.backends.tests.errors import Py4JJavaError
from ibis.backends.tests.errors import PsycoPg2InternalError, Py4JJavaError

pytestmark = [
pytest.mark.never(
Expand Down Expand Up @@ -39,6 +39,28 @@
)


@pytest.mark.notyet("clickhouse", reason="nested types can't be NULL")
@pytest.mark.broken(["pandas", "dask"], reason="TypeError: iteration over a 0-d array")
@pytest.mark.notimpl(
["risingwave"],
raises=PsycoPg2InternalError,
reason="function hstore(character varying[], character varying[]) does not exist",
)
@pytest.mark.parametrize(
("k", "v"),
[
param(["a", "b"], None, id="null_values"),
param(None, ["c", "d"], id="null_keys"),
param(None, None, id="null_both"),
],
)
def test_map_nulls(con, k, v):
k = ibis.literal(k, type="array<string>")
v = ibis.literal(v, type="array<string>")
m = ibis.map(k, v)
assert con.execute(m) is None


@pytest.mark.notimpl(["pandas", "dask"])
def test_map_table(backend):
table = backend.map
Expand Down

0 comments on commit 75d32e5

Please sign in to comment.