We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
array.query no longer works for string attributes
I have a case that looks basically like this:

```
array = tiledb.DenseArray(uri, mode="r")
q = array.query(attrs=["attrname"])
data = q[:]
```
This works when the type of the attribute is a float or an int.
But if the type of the attribute is a string, this fails with an exception:

```
  File "tiledb/libtiledb.pyx", line 3874, in tiledb.libtiledb.Query.__getitem__
  File "tiledb/libtiledb.pyx", line 4107, in tiledb.libtiledb.DenseArrayImpl.subarray
  File "tiledb/libtiledb.pyx", line 4156, in tiledb.libtiledb.DenseArrayImpl._read_dense_subarray
  File "tiledb/libtiledb.pyx", line 3760, in tiledb.libtiledb.Array._unpack_varlen_query
  File "tiledb/libtiledb.pyx", line 3760, in tiledb.libtiledb.Array._unpack_varlen_query
  File "tiledb/libtiledb.pyx", line 3805, in tiledb.libtiledb.Array._unpack_varlen_query
SystemError: Negative size passed to PyUnicode_FromStringAndSize
```

This worked in v1.7.6, and I ran into this problem when porting to 2.0.0.
If there is a better way to pull out just one attribute from the array, then
I'm open to suggestions.

[example.zip](https://github.com/TileDB-Inc/TileDB-Py/files/4589870/example.zip)

Attached is a script, output from 1.7.6, and output from 2.0.0





--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `tiledb/multirange_indexing.py`
Content:
```
1 import tiledb
2 from tiledb import Array, ArraySchema, TileDBError
3 import os, numpy as np
4 import sys, weakref
5 from collections import OrderedDict
6 
7 def mr_dense_result_shape(ranges, base_shape = None):
8     # assumptions: len(ranges) matches number of dims
9     if base_shape is not None:
10         assert len(ranges) == len(base_shape), "internal error: mismatched shapes"
11 
12     new_shape = list()
13     for i,rr in enumerate(ranges):
14         if rr != ():
15             m = list(map(lambda y: abs(y[1] - y[0]) + 1, rr))
16             new_shape.append(np.sum(m))
17         else:
18             if base_shape is None:
19                 raise ValueError("Missing required base_shape for whole-dimension slices")
20             # empty range covers dimension
21             new_shape.append(base_shape[i])
22 
23     return tuple(new_shape)
24 
25 def mr_dense_result_numel(ranges):
26     return np.prod(mr_dense_result_shape(ranges))
27 
28 def sel_to_subranges(dim_sel):
29     subranges = list()
30     for range in dim_sel:
31         if np.isscalar(range):
32             subranges.append( (range, range) )
33         elif isinstance(range, slice):
34             if range.step is not None:
35                 raise ValueError("Stepped slice ranges are not supported")
36             elif range.start is None and range.stop is None:
37                 # ':' full slice
38                 pass
39             else:
40                 subranges.append( (range.start, range.stop) )
41         elif isinstance(range, tuple):
42             subranges.extend((range,))
43         elif isinstance(range, list):
44             for el in range:
45                 subranges.append( (el, el) )
46         else:
47             raise TypeError("Unsupported selection ")
48 
49     return tuple(subranges)
50 
51 
52 class MultiRangeIndexer(object):
53     """
54     Implements multi-range / outer / orthogonal indexing.
55 
56     """
57 
58     def __init__(self, array, query = None):
59         if not issubclass(type(array), tiledb.Array):
60             raise ValueError("Internal error: MultiRangeIndexer expected tiledb.Array")
61         self.array_ref = weakref.ref(array)
62         self.schema = array.schema
63         self.query = query
64 
65     @property
66     def array(self):
67         assert self.array_ref() is not None, \
68             "Internal error: invariant violation (indexing call w/ dead array_ref)"
69         return self.array_ref()
70 
71     @classmethod
72     def __test_init__(cls, array):
73         """
74         Internal helper method for testing getitem range calculation.
75         :param array:
76         :return:
77         """
78         m = cls.__new__(cls)
79         m.array_ref = weakref.ref(array)
80         m.schema = array.schema
81         m.query = None
82         return m
83 
84     def getitem_ranges(self, idx):
85         dom = self.schema.domain
86         ndim = dom.ndim
87 
88         if isinstance(idx, tuple):
89             idx = list(idx)
90         else:
91             idx = [idx]
92 
93         ranges = list()
94         for i,sel in enumerate(idx):
95             if not isinstance(sel, list):
96                 sel = [sel]
97             subranges = sel_to_subranges(sel)
98             ranges.append(subranges)
99 
100         # extend the list to ndim
101         if len(ranges) < ndim:
102             ranges.extend([ tuple() for _ in range(ndim-len(ranges))])
103 
104         rval = tuple(ranges)
105         return rval
106 
107     def __getitem__(self, idx):
108         # implements multi-range / outer / orthogonal indexing
109         ranges = self.getitem_ranges(idx)
110 
111         schema = self.schema
112         dom = self.schema.domain
113         attr_names = tuple(self.schema.attr(i).name for i in range(self.schema.nattr))
114 
115         coords = None
116         if self.query is not None:
117             # if we are called via Query object, then we need to respect Query semantics
118             attr_names = tuple(self.query.attrs) if self.query.attrs else attr_names # query.attrs might be None -> all
119             coords = self.query.coords
120 
121         from tiledb.core import PyQuery
122         q = PyQuery(self.array._ctx_(), self.array, attr_names, coords)
123 
124         q.set_ranges(ranges)
125         q.submit()
126 
127         result_dict = OrderedDict(q.results())
128 
129         for name, item in result_dict.items():
130             if len(item[1]) > 0:
131                 arr = self.array._unpack_varlen_query(item, name)
132             else:
133                 arr = item[0]
134                 arr.dtype = schema.attr_or_dim_dtype(name)
135             result_dict[name] = arr
136 
137         if self.schema.sparse:
138             return result_dict
139         else:
140             result_shape = mr_dense_result_shape(ranges, self.schema.shape)
141             for arr in result_dict.values():
142                 # TODO check/test layout
143                 arr.shape = result_shape
144             return result_dict
145
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/tiledb/multirange_indexing.py b/tiledb/multirange_indexing.py
--- a/tiledb/multirange_indexing.py
+++ b/tiledb/multirange_indexing.py
@@ -128,7 +128,7 @@
 
         for name, item in result_dict.items():
             if len(item[1]) > 0:
-                arr = self.array._unpack_varlen_query(item, name)
+                arr = q.unpack_buffer(name, item[0], item[1])
             else:
                 arr = item[0]
                 arr.dtype = schema.attr_or_dim_dtype(name)

{"golden_diff": "diff --git a/tiledb/multirange_indexing.py b/tiledb/multirange_indexing.py\n--- a/tiledb/multirange_indexing.py\n+++ b/tiledb/multirange_indexing.py\n@@ -128,7 +128,7 @@\n \n         for name, item in result_dict.items():\n             if len(item[1]) > 0:\n-                arr = self.array._unpack_varlen_query(item, name)\n+                arr = q.unpack_buffer(name, item[0], item[1])\n             else:\n                 arr = item[0]\n                 arr.dtype = schema.attr_or_dim_dtype(name)\n", "issue": "array.query no longer works for string attributes\nI have a case that looks basically like this:\r\n\r\n```\r\narray = tiledb.DenseArray(uri, mode=\"r\")\r\nq = array.query(attrs=[\"attrname\"])\r\ndata = q[:]\r\n```\r\nThis works when the type of the attribute is a float or an int.\r\nBut if the type of the attribute is a string, this fails with an exception:\r\n\r\n```\r\n  File \"tiledb/libtiledb.pyx\", line 3874, in tiledb.libtiledb.Query.__getitem__\r\n  File \"tiledb/libtiledb.pyx\", line 4107, in tiledb.libtiledb.DenseArrayImpl.subarray\r\n  File \"tiledb/libtiledb.pyx\", line 4156, in tiledb.libtiledb.DenseArrayImpl._read_dense_subarray\r\n  File \"tiledb/libtiledb.pyx\", line 3760, in tiledb.libtiledb.Array._unpack_varlen_query\r\n  File \"tiledb/libtiledb.pyx\", line 3760, in tiledb.libtiledb.Array._unpack_varlen_query\r\n  File \"tiledb/libtiledb.pyx\", line 3805, in tiledb.libtiledb.Array._unpack_varlen_query\r\nSystemError: Negative size passed to PyUnicode_FromStringAndSize\r\n```\r\n\r\nThis worked in v1.7.6, and I ran into this problem when porting to 2.0.0.\r\nIf there is a better way to pull out just one attribute from the array, then\r\nI'm open to suggestions.\r\n\r\n[example.zip](https://github.com/TileDB-Inc/TileDB-Py/files/4589870/example.zip)\r\n\r\nAttached is a script, output from 1.7.6, and output from 2.0.0\r\n\r\n\r\n\r\n\n", "before_files": [{"content": "import tiledb\nfrom tiledb import Array, ArraySchema, TileDBError\nimport os, numpy as np\nimport sys, weakref\nfrom collections import OrderedDict\n\ndef mr_dense_result_shape(ranges, base_shape = None):\n    # assumptions: len(ranges) matches number of dims\n    if base_shape is not None:\n        assert len(ranges) == len(base_shape), \"internal error: mismatched shapes\"\n\n    new_shape = list()\n    for i,rr in enumerate(ranges):\n        if rr != ():\n            m = list(map(lambda y: abs(y[1] - y[0]) + 1, rr))\n            new_shape.append(np.sum(m))\n        else:\n            if base_shape is None:\n                raise ValueError(\"Missing required base_shape for whole-dimension slices\")\n            # empty range covers dimension\n            new_shape.append(base_shape[i])\n\n    return tuple(new_shape)\n\ndef mr_dense_result_numel(ranges):\n    return np.prod(mr_dense_result_shape(ranges))\n\ndef sel_to_subranges(dim_sel):\n    subranges = list()\n    for range in dim_sel:\n        if np.isscalar(range):\n            subranges.append( (range, range) )\n        elif isinstance(range, slice):\n            if range.step is not None:\n                raise ValueError(\"Stepped slice ranges are not supported\")\n            elif range.start is None and range.stop is None:\n                # ':' full slice\n                pass\n            else:\n                subranges.append( (range.start, range.stop) )\n        elif isinstance(range, tuple):\n            subranges.extend((range,))\n        elif isinstance(range, list):\n            for el in range:\n                subranges.append( (el, el) )\n        else:\n            raise TypeError(\"Unsupported selection \")\n\n    return tuple(subranges)\n\n\nclass MultiRangeIndexer(object):\n    \"\"\"\n    Implements multi-range / outer / orthogonal indexing.\n\n    \"\"\"\n\n    def __init__(self, array, query = None):\n        if not issubclass(type(array), tiledb.Array):\n            raise ValueError(\"Internal error: MultiRangeIndexer expected tiledb.Array\")\n        self.array_ref = weakref.ref(array)\n        self.schema = array.schema\n        self.query = query\n\n    @property\n    def array(self):\n        assert self.array_ref() is not None, \\\n            \"Internal error: invariant violation (indexing call w/ dead array_ref)\"\n        return self.array_ref()\n\n    @classmethod\n    def __test_init__(cls, array):\n        \"\"\"\n        Internal helper method for testing getitem range calculation.\n        :param array:\n        :return:\n        \"\"\"\n        m = cls.__new__(cls)\n        m.array_ref = weakref.ref(array)\n        m.schema = array.schema\n        m.query = None\n        return m\n\n    def getitem_ranges(self, idx):\n        dom = self.schema.domain\n        ndim = dom.ndim\n\n        if isinstance(idx, tuple):\n            idx = list(idx)\n        else:\n            idx = [idx]\n\n        ranges = list()\n        for i,sel in enumerate(idx):\n            if not isinstance(sel, list):\n                sel = [sel]\n            subranges = sel_to_subranges(sel)\n            ranges.append(subranges)\n\n        # extend the list to ndim\n        if len(ranges) < ndim:\n            ranges.extend([ tuple() for _ in range(ndim-len(ranges))])\n\n        rval = tuple(ranges)\n        return rval\n\n    def __getitem__(self, idx):\n        # implements multi-range / outer / orthogonal indexing\n        ranges = self.getitem_ranges(idx)\n\n        schema = self.schema\n        dom = self.schema.domain\n        attr_names = tuple(self.schema.attr(i).name for i in range(self.schema.nattr))\n\n        coords = None\n        if self.query is not None:\n            # if we are called via Query object, then we need to respect Query semantics\n            attr_names = tuple(self.query.attrs) if self.query.attrs else attr_names # query.attrs might be None -> all\n            coords = self.query.coords\n\n        from tiledb.core import PyQuery\n        q = PyQuery(self.array._ctx_(), self.array, attr_names, coords)\n\n        q.set_ranges(ranges)\n        q.submit()\n\n        result_dict = OrderedDict(q.results())\n\n        for name, item in result_dict.items():\n            if len(item[1]) > 0:\n                arr = self.array._unpack_varlen_query(item, name)\n            else:\n                arr = item[0]\n                arr.dtype = schema.attr_or_dim_dtype(name)\n            result_dict[name] = arr\n\n        if self.schema.sparse:\n            return result_dict\n        else:\n            result_shape = mr_dense_result_shape(ranges, self.schema.shape)\n            for arr in result_dict.values():\n                # TODO check/test layout\n                arr.shape = result_shape\n            return result_dict\n", "path": "tiledb/multirange_indexing.py"}], "after_files": [{"content": "import tiledb\nfrom tiledb import Array, ArraySchema, TileDBError\nimport os, numpy as np\nimport sys, weakref\nfrom collections import OrderedDict\n\ndef mr_dense_result_shape(ranges, base_shape = None):\n    # assumptions: len(ranges) matches number of dims\n    if base_shape is not None:\n        assert len(ranges) == len(base_shape), \"internal error: mismatched shapes\"\n\n    new_shape = list()\n    for i,rr in enumerate(ranges):\n        if rr != ():\n            m = list(map(lambda y: abs(y[1] - y[0]) + 1, rr))\n            new_shape.append(np.sum(m))\n        else:\n            if base_shape is None:\n                raise ValueError(\"Missing required base_shape for whole-dimension slices\")\n            # empty range covers dimension\n            new_shape.append(base_shape[i])\n\n    return tuple(new_shape)\n\ndef mr_dense_result_numel(ranges):\n    return np.prod(mr_dense_result_shape(ranges))\n\ndef sel_to_subranges(dim_sel):\n    subranges = list()\n    for range in dim_sel:\n        if np.isscalar(range):\n            subranges.append( (range, range) )\n        elif isinstance(range, slice):\n            if range.step is not None:\n                raise ValueError(\"Stepped slice ranges are not supported\")\n            elif range.start is None and range.stop is None:\n                # ':' full slice\n                pass\n            else:\n                subranges.append( (range.start, range.stop) )\n        elif isinstance(range, tuple):\n            subranges.extend((range,))\n        elif isinstance(range, list):\n            for el in range:\n                subranges.append( (el, el) )\n        else:\n            raise TypeError(\"Unsupported selection \")\n\n    return tuple(subranges)\n\n\nclass MultiRangeIndexer(object):\n    \"\"\"\n    Implements multi-range / outer / orthogonal indexing.\n\n    \"\"\"\n\n    def __init__(self, array, query = None):\n        if not issubclass(type(array), tiledb.Array):\n            raise ValueError(\"Internal error: MultiRangeIndexer expected tiledb.Array\")\n        self.array_ref = weakref.ref(array)\n        self.schema = array.schema\n        self.query = query\n\n    @property\n    def array(self):\n        assert self.array_ref() is not None, \\\n            \"Internal error: invariant violation (indexing call w/ dead array_ref)\"\n        return self.array_ref()\n\n    @classmethod\n    def __test_init__(cls, array):\n        \"\"\"\n        Internal helper method for testing getitem range calculation.\n        :param array:\n        :return:\n        \"\"\"\n        m = cls.__new__(cls)\n        m.array_ref = weakref.ref(array)\n        m.schema = array.schema\n        m.query = None\n        return m\n\n    def getitem_ranges(self, idx):\n        dom = self.schema.domain\n        ndim = dom.ndim\n\n        if isinstance(idx, tuple):\n            idx = list(idx)\n        else:\n            idx = [idx]\n\n        ranges = list()\n        for i,sel in enumerate(idx):\n            if not isinstance(sel, list):\n                sel = [sel]\n            subranges = sel_to_subranges(sel)\n            ranges.append(subranges)\n\n        # extend the list to ndim\n        if len(ranges) < ndim:\n            ranges.extend([ tuple() for _ in range(ndim-len(ranges))])\n\n        rval = tuple(ranges)\n        return rval\n\n    def __getitem__(self, idx):\n        # implements multi-range / outer / orthogonal indexing\n        ranges = self.getitem_ranges(idx)\n\n        schema = self.schema\n        dom = self.schema.domain\n        attr_names = tuple(self.schema.attr(i).name for i in range(self.schema.nattr))\n\n        coords = None\n        if self.query is not None:\n            # if we are called via Query object, then we need to respect Query semantics\n            attr_names = tuple(self.query.attrs) if self.query.attrs else attr_names # query.attrs might be None -> all\n            coords = self.query.coords\n\n        from tiledb.core import PyQuery\n        q = PyQuery(self.array._ctx_(), self.array, attr_names, coords)\n\n        q.set_ranges(ranges)\n        q.submit()\n\n        result_dict = OrderedDict(q.results())\n\n        for name, item in result_dict.items():\n            if len(item[1]) > 0:\n                arr = q.unpack_buffer(name, item[0], item[1])\n            else:\n                arr = item[0]\n                arr.dtype = schema.attr_or_dim_dtype(name)\n            result_dict[name] = arr\n\n        if self.schema.sparse:\n            return result_dict\n        else:\n            result_shape = mr_dense_result_shape(ranges, self.schema.shape)\n            for arr in result_dict.values():\n                # TODO check/test layout\n                arr.shape = result_shape\n            return result_dict\n", "path": "tiledb/multirange_indexing.py"}]}