This is an automated email from the ASF dual-hosted git repository.

pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 6b1ad60ab5e GH-50658: [C++][Dev] Add RunEndEncoded support to 
gdb_arrow.py (#50768)
6b1ad60ab5e is described below

commit 6b1ad60ab5e58adb32e2f54be61daeb51bc5c1c6
Author: fenfeng9 <[email protected]>
AuthorDate: Tue Oct 6 16:26:41 2026 +0800

    GH-50658: [C++][Dev] Add RunEndEncoded support to gdb_arrow.py (#50768)
    
    ### Rationale for this change
    The Arrow GDB pretty-printers do not support run-end encoded data types.
    
    ### What changes are included in this PR?
    Add GDB pretty-printer support and tests for `run-end encoded` types, 
scalars, and arrays.
    
    ### Are these changes tested?
    Yes.
    
    ### Are there any user-facing changes?
    No.
    
    * GitHub Issue: #50658
    
    Authored-by: fenfeng9 <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/gdb_arrow.py                       | 97 +++++++++++++++++++++++++++++++---
 python/pyarrow/src/arrow/python/gdb.cc | 29 ++++++++++
 python/pyarrow/tests/test_gdb.py       | 50 ++++++++++++++++++
 3 files changed, 170 insertions(+), 6 deletions(-)

diff --git a/cpp/gdb_arrow.py b/cpp/gdb_arrow.py
index bab1fa8722d..48956117732 100644
--- a/cpp/gdb_arrow.py
+++ b/cpp/gdb_arrow.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
+from bisect import bisect_right
 from collections import namedtuple
 from collections.abc import Sequence
 import datetime
@@ -95,7 +96,8 @@ def identity(v):
 
 
 def has_null_bitmap(type_id):
-    return type_id not in (Type.NA, Type.SPARSE_UNION, Type.DENSE_UNION)
+    return type_id not in (Type.NA, Type.SPARSE_UNION, Type.DENSE_UNION,
+                           Type.RUN_END_ENCODED)
 
 
 @lru_cache()
@@ -626,11 +628,12 @@ class Buffer:
         """
         Return a view over the bytes of this buffer.
         """
-        if self.size > 0:
-            if length is None:
-                length = self.size
+        if length is None:
+            length = self.size - offset
+        # Sliced arrays may share buffers, so only read the requested range.
+        if length > 0:
             mem = gdb.selected_inferior().read_memory(
-                self.val['data_'] + offset, self.size)
+                self.val['data_'] + offset, length)
         else:
             mem = memoryview(b"")
         # Read individual bytes as unsigned integers rather than
@@ -769,7 +772,8 @@ class Bitmap(Sequence):
     def from_buffer(cls, buf, offset, length):
         assert isinstance(buf, Buffer)
         byte_offset, bit_offset = divmod(offset, 8)
-        byte_length = math.ceil(length + offset / 8) - byte_offset
+        # E.g. offset=3, length=6 selects bits 3..8 and needs 2 bytes.
+        byte_length = math.ceil((bit_offset + length) / 8)
         return cls(buf.bytes_view(byte_offset, byte_length),
                    bit_offset, length)
 
@@ -1069,6 +1073,7 @@ type_reprs = {
     'SparseUnionType': 'sparse_union',
     'DenseUnionType': 'dense_union',
     'DictionaryType': 'dictionary',
+    'RunEndEncodedType': 'run_end_encoded',
     }
 
 
@@ -1170,6 +1175,20 @@ class ListTypePrinter(TypePrinter):
             return f"{self._format_type()}({child})"
 
 
+class RunEndEncodedTypePrinter(TypePrinter):
+    """
+    Pretty-printer for run-end encoded types.
+    """
+
+    def to_string(self):
+        fields = self.fields
+        if len(fields) != 2:
+            return f"{self._format_type()}<uninitialized or corrupt>"
+        run_end_type = fields[0].type
+        value_type = fields[1].type
+        return f"{self._format_type()}({run_end_type}, {value_type})"
+
+
 class FixedSizeListTypePrinter(ListTypePrinter):
     """
     Pretty-printer for fixed-size list type.
@@ -1465,6 +1484,18 @@ class BaseListScalarPrinter(ScalarPrinter):
         return f"{self._format_type()} of value {value}"
 
 
+class RunEndEncodedScalarPrinter(ScalarPrinter):
+    """
+    Pretty-printer for arrow::RunEndEncodedScalar.
+    """
+
+    def to_string(self):
+        if not self.is_valid:
+            return self._format_null()
+        value = deref(self.val['value'])
+        return f"{self._format_type()} of value {value}"
+
+
 class StructScalarPrinter(ScalarPrinter):
     """
     Pretty-printer for arrow::StructScalar.
@@ -1831,6 +1862,51 @@ class BinaryArrayDataPrinter(ArrayDataPrinter):
                 yield self._null_child(i)
 
 
+class RunEndEncodedArrayDataPrinter(ArrayDataPrinter):
+    """
+    ArrayDataPrinter specialization for run-end encoded arrays.
+    """
+
+    def __init__(self, name, val):
+        if self.length == 0:
+            return
+        child_data = StdVector(self.val['child_data'])
+        self._run_ends_printer = ArrayDataPrinter(
+            "arrow::ArrayData", deref(child_data[0]))
+        self._values_printer = ArrayDataPrinter(
+            "arrow::ArrayData", deref(child_data[1]))
+
+    def display_hint(self):
+        return "array"
+
+    def children(self):
+        if self.length == 0:
+            return
+        run_ends = self._run_ends_printer._unpacked_buffer_values(
+            1, self._run_ends_printer.type_id)
+        values = iter(self._values_printer.children() or ())
+        run_index = bisect_right(run_ends, self.offset)
+        # Advance to the value for the run containing the logical offset.
+        for _ in range(run_index + 1):
+            value = next(values, None)
+            if value is None:
+                return
+
+        logical_index = self.offset
+        logical_end = self.offset + self.length
+        # Expand each run into the logical elements visible in this slice.
+        while logical_index < logical_end:
+            run_end = run_ends[run_index]
+            for i in range(logical_index, min(run_end, logical_end)):
+                yield self._valid_child(i - self.offset, value[1])
+            logical_index = run_end
+            run_index += 1
+            if logical_index < logical_end:
+                value = next(values, None)
+                if value is None:
+                    return
+
+
 class ArrayPrinter:
     """
     Pretty-printer for arrow::Array and subclasses.
@@ -1999,6 +2075,13 @@ class FixedSizeListTypeClass(DataTypeClass):
     scalar_printer = BaseListScalarPrinter
 
 
+class RunEndEncodedTypeClass(DataTypeClass):
+    is_parametric = True
+    type_printer = RunEndEncodedTypePrinter
+    scalar_printer = RunEndEncodedScalarPrinter
+    array_data_printer = RunEndEncodedArrayDataPrinter
+
+
 class MapTypeClass(DataTypeClass):
     is_parametric = True
     type_printer = MapTypePrinter
@@ -2093,6 +2176,8 @@ type_traits_by_id = {
 
     Type.DICTIONARY: DataTypeTraits(DictionaryTypeClass, 'DictionaryType'),
     Type.EXTENSION: DataTypeTraits(ExtensionTypeClass, 'ExtensionType'),
+    Type.RUN_END_ENCODED: DataTypeTraits(RunEndEncodedTypeClass,
+                                         'RunEndEncodedType'),
 }
 
 
diff --git a/python/pyarrow/src/arrow/python/gdb.cc 
b/python/pyarrow/src/arrow/python/gdb.cc
index 4442cc43213..b2434b0a324 100644
--- a/python/pyarrow/src/arrow/python/gdb.cc
+++ b/python/pyarrow/src/arrow/python/gdb.cc
@@ -222,6 +222,9 @@ void TestSession() {
   FixedSizeListType fixed_size_list_type(float64(), 3);
   auto heap_fixed_size_list_type = fixed_size_list(float64(), 3);
 
+  RunEndEncodedType run_end_encoded_type(int32(), utf8());
+  auto heap_run_end_encoded_type = run_end_encoded(int32(), utf8());
+
   DictionaryType dict_type_unordered(int16(), utf8());
   DictionaryType dict_type_ordered(int16(), utf8(), /*ordered=*/true);
   auto heap_dict_type = dictionary(int16(), utf8());
@@ -389,6 +392,14 @@ void TestSession() {
   FixedSizeListScalar fixed_size_list_scalar_null{
       list_value_array, fixed_size_list(int32(), 3), /*is_valid=*/false};
 
+  auto run_end_encoded_scalar_type = run_end_encoded(int32(), utf8());
+  RunEndEncodedScalar run_end_encoded_scalar{MakeScalar("foo"),
+                                             run_end_encoded_scalar_type};
+  RunEndEncodedScalar run_end_encoded_scalar_null{run_end_encoded_scalar_type};
+  std::shared_ptr<Scalar> heap_run_end_encoded_scalar =
+      std::make_shared<RunEndEncodedScalar>(MakeScalar("foo"),
+                                            run_end_encoded_scalar_type);
+
   auto struct_scalar_type = struct_({field("ints", int32()), field("strs", 
utf8())});
   StructScalar struct_scalar{
       ScalarVector{MakeScalar(int32_t(42)), MakeScalar("some text")}, 
struct_scalar_type};
@@ -448,6 +459,24 @@ void TestSession() {
   auto heap_list_array = SliceArrayFromJSON(list(int64()), "[[1, 2], null, 
[]]");
   ListArray list_array{heap_list_array->data()};
 
+  // Encodes ["foo", "foo", null, null, null].
+  auto run_end_encoded_run_ends = SliceArrayFromJSON(int32(), "[2, 5]");
+  auto run_end_encoded_values = SliceArrayFromJSON(utf8(), R"(["foo", null])");
+  std::shared_ptr<Array> heap_run_end_encoded_array = 
*RunEndEncodedArray::Make(
+      /*logical_length=*/5, run_end_encoded_run_ends, run_end_encoded_values);
+  RunEndEncodedArray run_end_encoded_array{heap_run_end_encoded_array->data()};
+  auto heap_run_end_encoded_array_sliced = 
heap_run_end_encoded_array->Slice(1, 3);
+
+  // Sliced children
+  std::shared_ptr<Array> heap_ree_sliced = *RunEndEncodedArray::Make(
+      /*logical_length=*/3, SliceArrayFromJSON(int32(), "[1, 2, 5, 0]", 1, 2),
+      SliceArrayFromJSON(utf8(), R"(["bar", "baz", "foo", null, "qux"])", 2, 
2),
+      /*logical_offset=*/1);
+
+  std::shared_ptr<Array> heap_ree_int64 = *RunEndEncodedArray::Make(
+      /*logical_length=*/3, SliceArrayFromJSON(int64(), "[1, 3]"),
+      SliceArrayFromJSON(int32(), "[42, null]"));
+
   const char* json_double_array = "[-1.5, null]";
   auto heap_double_array = SliceArrayFromJSON(float64(), json_double_array);
 
diff --git a/python/pyarrow/tests/test_gdb.py b/python/pyarrow/tests/test_gdb.py
index cd44e686bb8..f6363ef7f45 100644
--- a/python/pyarrow/tests/test_gdb.py
+++ b/python/pyarrow/tests/test_gdb.py
@@ -407,6 +407,9 @@ def test_types_stack(gdb_arrow):
                      "arrow::large_list(arrow::large_utf8())")
     check_stack_repr(gdb_arrow, "fixed_size_list_type",
                      "arrow::fixed_size_list(arrow::float64(), 3)")
+    check_stack_repr(
+        gdb_arrow, "run_end_encoded_type",
+        "arrow::run_end_encoded(arrow::int32(), arrow::utf8())")
     check_stack_repr(
         gdb_arrow, "map_type_unsorted",
         "arrow::map(arrow::utf8(), arrow::binary(), keys_sorted=false)")
@@ -468,6 +471,9 @@ def test_types_heap(gdb_arrow):
                     "arrow::large_list(arrow::large_utf8())")
     check_heap_repr(gdb_arrow, "heap_fixed_size_list_type",
                     "arrow::fixed_size_list(arrow::float64(), 3)")
+    check_heap_repr(
+        gdb_arrow, "heap_run_end_encoded_type",
+        "arrow::run_end_encoded(arrow::int32(), arrow::utf8())")
     check_heap_repr(
         gdb_arrow, "heap_map_type",
         "arrow::map(arrow::utf8(), arrow::binary(), keys_sorted=false)")
@@ -744,6 +750,14 @@ def test_scalars_stack(gdb_arrow):
         gdb_arrow, "fixed_size_list_scalar_null",
         ('arrow::FixedSizeListScalar of type '
          'arrow::fixed_size_list(arrow::int32(), 3), null value'))
+    check_stack_repr(
+        gdb_arrow, "run_end_encoded_scalar",
+        ('arrow::RunEndEncodedScalar of value '
+         'arrow::StringScalar of size 3, value "foo"'))
+    check_stack_repr(
+        gdb_arrow, "run_end_encoded_scalar_null",
+        ('arrow::RunEndEncodedScalar of type '
+         'arrow::run_end_encoded(arrow::int32(), arrow::utf8()), null value'))
 
     check_stack_repr(
         gdb_arrow, "struct_scalar",
@@ -810,6 +824,10 @@ def test_scalars_heap(gdb_arrow):
         gdb_arrow, "heap_map_scalar_null",
         ('arrow::MapScalar of type arrow::map(arrow::utf8(), arrow::int32(), '
          'keys_sorted=false), null value'))
+    check_heap_repr(
+        gdb_arrow, "heap_run_end_encoded_scalar",
+        ('arrow::RunEndEncodedScalar of value '
+         'arrow::StringScalar of size 3, value "foo"'))
 
 
 def test_array_data(gdb_arrow):
@@ -828,6 +846,13 @@ def test_arrays_stack(gdb_arrow):
         gdb_arrow, "list_array",
         ("arrow::ListArray of type arrow::list(arrow::int64()), "
          "length 3, offset 0, null count 1"))
+    check_stack_repr(
+        gdb_arrow, "run_end_encoded_array",
+        ("arrow::RunEndEncodedArray of type "
+         "arrow::run_end_encoded(arrow::int32(), arrow::utf8()), "
+         "length 5, offset 0, null count 0 = "
+         "{[0] = \"foo\", [1] = \"foo\", [2] = null, "
+         "[3] = null, [4] = null}"))
 
 
 def test_arrays_heap(gdb_arrow):
@@ -1081,6 +1106,31 @@ def test_arrays_heap(gdb_arrow):
         gdb_arrow, "heap_list_array",
         ("arrow::ListArray of type arrow::list(arrow::int64()), "
          "length 3, offset 0, null count 1"))
+    check_heap_repr(
+        gdb_arrow, "heap_run_end_encoded_array",
+        ("arrow::RunEndEncodedArray of type "
+         "arrow::run_end_encoded(arrow::int32(), arrow::utf8()), "
+         "length 5, offset 0, null count 0 = "
+         "{[0] = \"foo\", [1] = \"foo\", [2] = null, "
+         "[3] = null, [4] = null}"))
+    check_heap_repr(
+        gdb_arrow, "heap_run_end_encoded_array_sliced",
+        ("arrow::RunEndEncodedArray of type "
+         "arrow::run_end_encoded(arrow::int32(), arrow::utf8()), "
+         "length 3, offset 1, null count 0 = "
+         "{[0] = \"foo\", [1] = null, [2] = null}"))
+    check_heap_repr(
+        gdb_arrow, "heap_ree_sliced",
+        ("arrow::RunEndEncodedArray of type "
+         "arrow::run_end_encoded(arrow::int32(), arrow::utf8()), "
+         "length 3, offset 1, null count 0 = "
+         "{[0] = \"foo\", [1] = null, [2] = null}"))
+    check_heap_repr(
+        gdb_arrow, "heap_ree_int64",
+        ("arrow::RunEndEncodedArray of type "
+         "arrow::run_end_encoded(arrow::int64(), arrow::int32()), "
+         "length 3, offset 0, null count 0 = "
+         "{[0] = 42, [1] = null, [2] = null}"))
 
 
 def test_schema(gdb_arrow):

Reply via email to