diff --git a/tiledb/multirange_indexing.py b/tiledb/multirange_indexing.py index 0ec7b788d1..dbbe32788a 100644 --- a/tiledb/multirange_indexing.py +++ b/tiledb/multirange_indexing.py @@ -17,6 +17,7 @@ Optional, Sequence, Tuple, + TypeVar, Union, cast, ) @@ -44,8 +45,17 @@ current_timer: ContextVar[str] = ContextVar("timer_scope") -# sentinel value to denote selecting an empty range -EmptyRange = object() +# sentinel type to denote selecting an empty range +EmptyRange = TypeVar("EmptyRange") + + +def is_empty_range(idx: Union[EmptyRange, List, Tuple]) -> bool: + if idx is not EmptyRange: + if hasattr(idx, "__len__") and len(idx) == 0 and idx != "": + return True + return False + return True + # TODO: expand with more accepted scalar types Scalar = Real @@ -248,7 +258,7 @@ def return_incomplete(self) -> bool: def __getitem__(self, idx): with timing("getitem_time"): - if idx is EmptyRange: + if is_empty_range(idx): self.pyquery = None self.subarray = None else: diff --git a/tiledb/tests/test_multi_index-hp.py b/tiledb/tests/test_multi_index-hp.py index 5d6f67f46d..08e6f1192b 100644 --- a/tiledb/tests/test_multi_index-hp.py +++ b/tiledb/tests/test_multi_index-hp.py @@ -39,7 +39,12 @@ def _direct_query_ranges(array: SparseArray, ranges, order): q.set_subarray(subarray) q.submit() - return {k: v[0].view(array.attr(0).dtype) for k, v in q.results().items()} + + if ranges == [[]]: + # empty range should give empty result + return {k: [] for k in q.results()} + else: + return {k: v[0].view(array.attr(0).dtype) for k, v in q.results().items()} # Compound strategies to build valid inputs for multi_index @@ -90,7 +95,6 @@ def create_array(uri): def test_multi_index_two_way_query(self, order, ranges, sparse_array_1d): """This test checks the result of "direct" range queries using PyQuery against the result of `multi_index` on the same ranges.""" - uri = sparse_array_1d assert isinstance(uri, str) diff --git a/tiledb/tests/test_multi_index.py b/tiledb/tests/test_multi_index.py index 377f5c7033..115ef4370e 100644 --- a/tiledb/tests/test_multi_index.py +++ b/tiledb/tests/test_multi_index.py @@ -903,6 +903,30 @@ def test_fixed_width_char(self): with tiledb.open(uri, mode="r") as A: assert all(A.query(use_arrow=True).df[:][""] == data) + @pytest.mark.skipif(not has_pandas(), reason="pandas>=1.0,<3.0 not installed") + def test_empty_idx(self): + uri = self.path("test_empty_idx") + + schema = tiledb.ArraySchema( + domain=tiledb.Domain(tiledb.Dim(name="dim", domain=(0, 9), dtype=np.uint8)), + sparse=True, + attrs=[tiledb.Attr(name="a", dtype=np.float64)], + ) + tiledb.Array.create(uri, schema) + + data = np.array(np.random.randint(10, size=10), dtype=np.float64) + + with tiledb.open(uri, mode="w") as A: + A[np.arange(10)] = data + + with tiledb.open(uri, mode="r") as A: + assert_array_equal(A.df[tiledb.EmptyRange]["a"], []) + assert_array_equal(A.multi_index[tiledb.EmptyRange]["a"], []) + assert_array_equal(A.df[[]]["a"], []) + assert_array_equal(A.multi_index[[]]["a"], []) + assert_array_equal(A.df[()]["a"], []) + assert_array_equal(A.multi_index[()]["a"], []) + # parametrize dtype and sparse @pytest.mark.parametrize(