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 f69ec05524 GH-50993: [CI][Integration] Add extension-wrapped union to 
integration data (#51027)
f69ec05524 is described below

commit f69ec05524b0d6ed44c3fa804377332dfc085fac
Author: alb3e3 <[email protected]>
AuthorDate: Wed Sep 2 16:37:38 2026 +0200

    GH-50993: [CI][Integration] Add extension-wrapped union to integration data 
(#51027)
    
    ### Rationale for this change
    
    Just as the C++ test suite gained coverage for extension-wrapped unions in 
#50927 (GH-50623), the cross-implementation integration tests should exercise 
this case so that every tested implementation handles an extension type whose 
storage is a union.
    
    ### What changes are included in this PR?
    
    A new archery integration datagen case, `extension_union`, with two columns:
    - `sparse_union_ext`: a sparse union (`int32`/`utf8`) wrapped in an 
extension type
    - `dense_union_ext`: a dense union (`int16`/`binary`) wrapped in an 
extension type
    
    `ExtensionField` already delegates its storage type/children/generation to 
the wrapped field, so no new datagen machinery is needed.
    
    ### Are these changes tested?
    
    The generator produces valid integration JSON locally (schema carries the 
`ARROW:extension:*` metadata on the union-typed fields, children preserved, 
batch sizes `[0, 7]`). The cross-implementation integration matrix in CI is the 
real test here — I've intentionally added the case with no per-implementation 
skips so the matrix can show which implementations still need a temporary 
`.skip_tester(...)`; I'll add those (with tracking links) based on the results.
    
    ### Are there any user-facing changes?
    
    No. This only adds integration-test data.
    
    Closes #50993.
    * GitHub Issue: #50993
    
    Lead-authored-by: Alb3e3 <[email protected]>
    Co-authored-by: Antoine Pitrou <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/src/arrow/c/bridge.cc                          |  9 +++++---
 cpp/src/arrow/c/bridge_test.cc                     | 10 ++++++++-
 .../integration_tests/test_integration_client.cc   |  5 +++--
 .../integration/c_data_integration_internal.cc     |  3 ++-
 cpp/src/arrow/integration/json_integration_test.cc |  4 +++-
 cpp/src/arrow/integration/json_internal.cc         |  2 +-
 cpp/src/arrow/testing/extension_type.h             |  6 ++++++
 cpp/src/arrow/testing/gtest_util.cc                | 14 ++++++++++++
 dev/archery/archery/integration/datagen.py         | 25 ++++++++++++++++++++++
 9 files changed, 69 insertions(+), 9 deletions(-)

diff --git a/cpp/src/arrow/c/bridge.cc b/cpp/src/arrow/c/bridge.cc
index 4391d4cbc2..184be3ab8e 100644
--- a/cpp/src/arrow/c/bridge.cc
+++ b/cpp/src/arrow/c/bridge.cc
@@ -579,16 +579,19 @@ struct ArrayExporter {
     // This is because ARROW-9037 is in version 0.17 and 0.17.1, and they are
     // not able to import arrays without a null bitmap and null_count == -1.
     data->GetNullCount();
+
+    const auto physical_type_id = data->type->storage_id();
+
     // Store buffer pointers
     size_t n_buffers = data->buffers.size();
     auto buffers_begin = data->buffers.begin();
-    if (n_buffers > 0 && 
!internal::may_have_validity_bitmap(data->type->id())) {
+    if (n_buffers > 0 && 
!internal::may_have_validity_bitmap(physical_type_id)) {
       --n_buffers;
       ++buffers_begin;
     }
 
-    bool need_variadic_buffer_sizes = data->type->storage_id() == 
Type::BINARY_VIEW ||
-                                      data->type->storage_id() == 
Type::STRING_VIEW;
+    bool need_variadic_buffer_sizes =
+        physical_type_id == Type::BINARY_VIEW || physical_type_id == 
Type::STRING_VIEW;
     if (need_variadic_buffer_sizes) {
       ++n_buffers;
     }
diff --git a/cpp/src/arrow/c/bridge_test.cc b/cpp/src/arrow/c/bridge_test.cc
index 4372732675..9a4d104d00 100644
--- a/cpp/src/arrow/c/bridge_test.cc
+++ b/cpp/src/arrow/c/bridge_test.cc
@@ -575,9 +575,15 @@ struct ArrayExportChecker {
     ASSERT_EQ(c_export->null_count, expected_data.null_count);
     ASSERT_EQ(c_export->offset, expected_data.offset);
 
+    const DataType* physical_type = expected_data.type.get();
+    if (physical_type->id() == Type::EXTENSION) {
+      physical_type =
+          checked_cast<const 
ExtensionType&>(*physical_type).storage_type().get();
+    }
+
     auto expected_n_buffers = 
static_cast<int64_t>(expected_data.buffers.size());
     auto expected_buffers = expected_data.buffers.data();
-    if (!internal::may_have_validity_bitmap(expected_data.type->id())) {
+    if (!internal::may_have_validity_bitmap(physical_type->id())) {
       --expected_n_buffers;
       ++expected_buffers;
     }
@@ -1173,6 +1179,8 @@ TEST_F(TestArrayExport, Extension) {
   TestPrimitive(ExampleUuid);
   TestPrimitive(ExampleSmallint);
   TestPrimitive(ExampleComplex128);
+  TestPrimitive(ExampleDenseUnionExtension);
+  TestPrimitive(ExampleSparseUnionExtension);
 }
 
 TEST_F(TestArrayExport, MovePrimitive) {
diff --git a/cpp/src/arrow/flight/integration_tests/test_integration_client.cc 
b/cpp/src/arrow/flight/integration_tests/test_integration_client.cc
index c5c3f10576..f39a4f04eb 100644
--- a/cpp/src/arrow/flight/integration_tests/test_integration_client.cc
+++ b/cpp/src/arrow/flight/integration_tests/test_integration_client.cc
@@ -145,8 +145,9 @@ class IntegrationTestScenario : public Scenario {
 
   Status RunClient(std::unique_ptr<FlightClient> client) override {
     // Make sure the required extension types are registered.
-    ExtensionTypeGuard uuid_ext_guard(uuid());
-    ExtensionTypeGuard dict_ext_guard(dict_extension_type());
+    ExtensionTypeGuard ext_guard({uuid(), dict_extension_type(),
+                                  dense_union_extension_type(),
+                                  sparse_union_extension_type()});
 
     FlightDescriptor descr{FlightDescriptor::PATH, "", {FLAGS_path}};
 
diff --git a/cpp/src/arrow/integration/c_data_integration_internal.cc 
b/cpp/src/arrow/integration/c_data_integration_internal.cc
index b21a0cc13b..80b1758086 100644
--- a/cpp/src/arrow/integration/c_data_integration_internal.cc
+++ b/cpp/src/arrow/integration/c_data_integration_internal.cc
@@ -38,7 +38,8 @@ namespace {
 
 // Make sure the extension types referenced in test data are registered.
 [[nodiscard]] auto RequireExtensionTypes() {
-  return ExtensionTypeGuard({uuid(), dict_extension_type()});
+  return ExtensionTypeGuard({uuid(), dict_extension_type(), 
dense_union_extension_type(),
+                             sparse_union_extension_type()});
 }
 
 template <typename Func>
diff --git a/cpp/src/arrow/integration/json_integration_test.cc 
b/cpp/src/arrow/integration/json_integration_test.cc
index 98bbd3f56b..0f7e6c85dc 100644
--- a/cpp/src/arrow/integration/json_integration_test.cc
+++ b/cpp/src/arrow/integration/json_integration_test.cc
@@ -225,7 +225,9 @@ Status RunCommand(const std::string& json_path, const 
std::string& arrow_path,
                   const std::string& command) {
   // Make sure the required extension types are registered, as they will be
   // referenced in test data.
-  ExtensionTypeGuard ext_guard({uuid(), dict_extension_type()});
+  ExtensionTypeGuard ext_guard({uuid(), dict_extension_type(),
+                                dense_union_extension_type(),
+                                sparse_union_extension_type()});
 
   if (json_path == "") {
     return Status::Invalid("Must specify json file name");
diff --git a/cpp/src/arrow/integration/json_internal.cc 
b/cpp/src/arrow/integration/json_internal.cc
index 0f72207bd4..9dd9963cd5 100644
--- a/cpp/src/arrow/integration/json_internal.cc
+++ b/cpp/src/arrow/integration/json_internal.cc
@@ -1952,7 +1952,7 @@ class ArrayReader {
   Result<std::shared_ptr<ArrayData>> Parse() {
     ARROW_ASSIGN_OR_RAISE(length_, GetMemberInt<int32_t>(obj_, "count"));
 
-    if (::arrow::internal::may_have_validity_bitmap(type_->id())) {
+    if (::arrow::internal::may_have_validity_bitmap(type_->storage_id())) {
       // Null and union types don't have a validity bitmap
       RETURN_NOT_OK(ParseValidityBitmap());
     }
diff --git a/cpp/src/arrow/testing/extension_type.h 
b/cpp/src/arrow/testing/extension_type.h
index e5d6b597c5..f931206a0d 100644
--- a/cpp/src/arrow/testing/extension_type.h
+++ b/cpp/src/arrow/testing/extension_type.h
@@ -266,6 +266,12 @@ std::shared_ptr<Array> ExampleDictExtension();
 ARROW_TESTING_EXPORT
 std::shared_ptr<Array> ExampleComplex128();
 
+ARROW_TESTING_EXPORT
+std::shared_ptr<Array> ExampleDenseUnionExtension();
+
+ARROW_TESTING_EXPORT
+std::shared_ptr<Array> ExampleSparseUnionExtension();
+
 ARROW_TESTING_EXPORT
 std::shared_ptr<Array> MakeComplex128(const std::shared_ptr<Array>& real,
                                       const std::shared_ptr<Array>& imag);
diff --git a/cpp/src/arrow/testing/gtest_util.cc 
b/cpp/src/arrow/testing/gtest_util.cc
index daadfe9c2c..b7d2a963d0 100644
--- a/cpp/src/arrow/testing/gtest_util.cc
+++ b/cpp/src/arrow/testing/gtest_util.cc
@@ -1092,6 +1092,20 @@ std::shared_ptr<Array> ExampleComplex128() {
   return ExtensionType::WrapArray(complex128(), arr);
 }
 
+std::shared_ptr<Array> ExampleDenseUnionExtension() {
+  auto type = dense_union_extension_type();
+  auto storage_type = checked_cast<const ExtensionType&>(*type).storage_type();
+  return ExtensionType::WrapArray(
+      type, ArrayFromJSON(storage_type, R"([[0, 1.5], [1, "abc"]])"));
+}
+
+std::shared_ptr<Array> ExampleSparseUnionExtension() {
+  auto type = sparse_union_extension_type();
+  auto storage_type = checked_cast<const ExtensionType&>(*type).storage_type();
+  return ExtensionType::WrapArray(
+      type, ArrayFromJSON(storage_type, R"([[0, 1.5], [1, "abc"]])"));
+}
+
 ExtensionTypeGuard::ExtensionTypeGuard(const std::shared_ptr<DataType>& type)
     : ExtensionTypeGuard(DataTypeVector{type}) {}
 
diff --git a/dev/archery/archery/integration/datagen.py 
b/dev/archery/archery/integration/datagen.py
index ed50919dfc..adffb2a1b2 100644
--- a/dev/archery/archery/integration/datagen.py
+++ b/dev/archery/archery/integration/datagen.py
@@ -1938,6 +1938,29 @@ def generate_extension_case():
                           dictionaries=[dict0])
 
 
+def generate_extension_wrapped_union_case():
+    # Unions wrapped in an extension type, exercising the extension/union
+    # interaction across implementations (see the C++ fix in GH-50623).
+    sparse_union_type = ExtensionType(
+        'sparse-union-extension', 'sparse-union-extension',
+        SparseUnionField('', [get_field('floats', 'float64'),
+                              get_field('strings', 'largeutf8')],
+                         type_ids=[0, 1]))
+    dense_union_type = ExtensionType(
+        'dense-union-extension', 'dense-union-extension',
+        DenseUnionField('', [get_field('floats', 'float64'),
+                             get_field('strings', 'largeutf8')],
+                        type_ids=[0, 1]))
+
+    fields = [
+        ExtensionField('sparse_union_ext', sparse_union_type),
+        ExtensionField('dense_union_ext', dense_union_type),
+    ]
+
+    batch_sizes = [0, 7]
+    return _generate_file("extension_union", fields, batch_sizes)
+
+
 def get_generated_json_files(tempdir=None):
     tempdir = tempdir or tempfile.mkdtemp(prefix='arrow-integration-')
 
@@ -2047,6 +2070,8 @@ def get_generated_json_files(tempdir=None):
         # TODO(https://github.com/apache/arrow/issues/38045)
         .skip_format(SKIP_FLIGHT, '.NET')
         .skip_tester('Ruby'),
+
+        generate_extension_wrapped_union_case(),
     ]
 
     generated_paths = []

Reply via email to