prompt: 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()
  
  ```
  
golden_diff: 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)
  
verification_info: {"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"}]}