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

alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git


The following commit(s) were added to refs/heads/master by this push:
     new 16689973a KUDU-1261 instantiate templates for RowPtr::GetArray()
16689973a is described below

commit 16689973a72e03649898c568d7ab423bc4bb8a35
Author: Alexey Serbin <[email protected]>
AuthorDate: Thu Oct 2 20:42:23 2025 -0700

    KUDU-1261 instantiate templates for RowPtr::GetArray()
    
    This patch adds KuduScanBatch::RowPtr::GetArray<>() template
    instantiations for all the necessary column data types.
    
    Since Kudu C++ client API still officially supports C++98, it's
    necessary to have template instantiations for RowPtr::GetArray(),
    similar to RowPtr::Get() for non-array getters.  Otherwise, there
    might be linker errors: see [1] for details.
    
    This patch also contains a fix for a crash if trying to read an empty
    boolean array using the RowPtr::GetArray typed accessor.
    The corresponding end-to-end tests are included in client-test.cc,
    along with scenarios of calling the RowPtr::GetArray() on a column
    that is a null array.
    
    [1] 
https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl
    
    Change-Id: I50b36c48959b00874921c6b56d210cf7358619f4
    Reviewed-on: http://gerrit.cloudera.org:8080/23490
    Reviewed-by: Abhishek Chennaka <[email protected]>
    Tested-by: Alexey Serbin <[email protected]>
---
 src/kudu/client/client-test.cc | 244 ++++++++++++++++++++++++++++++++++++++++-
 src/kudu/client/scan_batch.cc  | 198 +++++++++++++++++++++++++++------
 src/kudu/client/scan_batch.h   |   1 +
 src/kudu/common/partial_row.h  |   2 +
 4 files changed, 407 insertions(+), 38 deletions(-)

diff --git a/src/kudu/client/client-test.cc b/src/kudu/client/client-test.cc
index e8e8ad434..7211e8c93 100644
--- a/src/kudu/client/client-test.cc
+++ b/src/kudu/client/client-test.cc
@@ -35,7 +35,6 @@
 #include <thread>
 #include <tuple>
 #include <type_traits>
-#include <unordered_map>
 #include <unordered_set>
 #include <utility>
 #include <vector>
@@ -47,6 +46,7 @@
 #include <google/protobuf/util/message_differencer.h>
 #include <gtest/gtest.h>
 
+#include "kudu/client/array_cell.h"
 #include "kudu/client/batcher.h"
 #include "kudu/client/callbacks.h"
 #include "kudu/client/client-internal.h"
@@ -61,6 +61,7 @@
 #include "kudu/client/scan_configuration.h"
 #include "kudu/client/scan_predicate.h"
 #include "kudu/client/scanner-internal.h"
+#include "kudu/client/schema-internal.h"
 #include "kudu/client/schema.h"
 #include "kudu/client/session-internal.h"
 #include "kudu/client/shared_ptr.h" // IWYU pragma: keep
@@ -70,11 +71,13 @@
 #include "kudu/client/write_op.h"
 #include "kudu/clock/clock.h"
 #include "kudu/clock/hybrid_clock.h"
+#include "kudu/common/common.pb.h"
 #include "kudu/common/partial_row.h"
 #include "kudu/common/row.h"
 #include "kudu/common/schema.h"
 #include "kudu/common/timestamp.h"
 #include "kudu/common/txn_id.h"
+#include "kudu/common/types.h"
 #include "kudu/common/wire_protocol.h"
 #include "kudu/common/wire_protocol.pb.h"
 #include "kudu/consensus/metadata.pb.h"
@@ -227,8 +230,8 @@ using std::pair;
 using std::set;
 using std::string;
 using std::thread;
+using std::tuple;
 using std::unique_ptr;
-using std::unordered_map;
 using std::unordered_set;
 using std::vector;
 using strings::Substitute;
@@ -895,6 +898,58 @@ class ClientTest : public KuduTest {
     return client_->OpenTable(table_name, table);
   }
 
+  // Create table with INT64 'key' column and array columns of the specified
+  // scalar types in 'array_elem_types'. The key column is named 'key', and
+  // the array columns named 'arr_x', where 'x' is the index corresponding
+  // to the scalar type in the 'x' index of the 'array_elem_types' vector.
+  Status CreateTableWithArrayColumns(
+      const string& table_name,
+      const vector<KuduColumnSchema::DataType>& array_elem_types,
+      const KuduColumnTypeAttributes& attributes = {},
+      KuduSchema* result_table_schema = nullptr) {
+    KuduSchemaBuilder builder;
+    
builder.AddColumn("key")->Type(KuduColumnSchema::INT64)->NotNull()->PrimaryKey();
+    for (size_t idx = 0; idx < array_elem_types.size(); ++idx) {
+      const auto elem_type = array_elem_types[idx];
+      const string col_name = Substitute("arr_$0", idx);
+      KuduColumnSchema::KuduArrayTypeDescriptor array_info(elem_type);
+      KuduColumnSchema::KuduNestedTypeDescriptor nested_type_info(array_info);
+      auto* col_spec = builder.AddColumn(col_name)->
+          NestedType(nested_type_info)->
+          Encoding(KuduColumnStorageAttributes::PLAIN_ENCODING);
+      // Set mandatory attributes for the columns of particular types.
+      if (elem_type == KuduColumnSchema::DECIMAL) {
+        col_spec->
+            Precision(attributes.precision())->
+            Scale(attributes.scale());
+      } else if (elem_type == KuduColumnSchema::VARCHAR) {
+        col_spec->Length(attributes.length());
+      }
+    }
+    KuduSchema schema;
+    RETURN_NOT_OK(builder.Build(&schema));
+
+    unique_ptr<KuduPartialRow> lower_bound(schema.NewRow());
+    RETURN_NOT_OK(lower_bound->SetInt64("key", -1));
+    unique_ptr<KuduPartialRow> upper_bound(schema.NewRow());
+    RETURN_NOT_OK(upper_bound->SetInt64("key", 99));
+
+    unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+    table_creator->add_range_partition(lower_bound.release(), 
upper_bound.release(),
+                                        KuduTableCreator::EXCLUSIVE_BOUND,
+                                        KuduTableCreator::INCLUSIVE_BOUND);
+    RETURN_NOT_OK(table_creator->table_name(table_name)
+        .schema(&schema)
+        .num_replicas(1)
+        .set_range_partition_columns({ "key" })
+        .Create());
+
+    if (result_table_schema) {
+      *result_table_schema = std::move(schema);
+    }
+    return Status::OK();
+  }
+
   // Kills a tablet server.
   // Boolean flags control whether to restart the tserver, and if so, whether 
to wait for it to
   // finish bootstrapping.
@@ -10604,5 +10659,190 @@ TEST_F(ClientTestMetacache, 
TestClientMetacacheInvalidation) {
   FLAGS_prevent_kudu_3461_infinite_recursion = true;
   TestClientMetacacheHelper(CURRENT_TEST_NAME());
 }
+
+namespace {
+const vector<DataType> kArrayElemTypes = {
+  DataType::BOOL,
+  DataType::INT8,
+  DataType::INT16,
+  DataType::INT32,
+  DataType::INT64,
+  DataType::BINARY,
+  DataType::STRING,
+  DataType::FLOAT,
+  DataType::DOUBLE,
+  DataType::UNIXTIME_MICROS,
+  DataType::DATE,
+  DataType::DECIMAL32,
+  DataType::DECIMAL64,
+  DataType::VARCHAR,
+};
+} // anonymous namespace
+
+class ArrayColumnParamTest :
+    public ClientTest,
+    public ::testing::WithParamInterface<tuple<DataType, bool, bool>> {
+ public:
+
+  template<DataType T>
+  void TestEmptyAndNullArraysImpl() {
+    typedef ArrayTypeTraits<T> ArrayType;
+    typedef typename ArrayTypeTraits<T>::element_cpp_type ElementType;
+
+    const bool is_null_array_cell = std::get<1>(GetParam());
+    const bool flush_tablet_data = std::get<2>(GetParam());
+    const string table_name = Substitute("table_ea_$0_$1",
+        flush_tablet_data ? "flush" : "noflush", DataType_Name(T));
+
+    KuduColumnTypeAttributes attrs;
+    if constexpr (T == DECIMAL32) {
+      attrs = KuduColumnTypeAttributes(6, 2);
+    } else if constexpr (T == DECIMAL64) {
+      attrs = KuduColumnTypeAttributes(18, 2);
+    } else if constexpr (T == VARCHAR) {
+      attrs = KuduColumnTypeAttributes(10);
+    }
+    ASSERT_OK(CreateTableWithArrayColumns(
+        table_name, { FromInternalDataType(T) }, attrs));
+
+    shared_ptr<KuduTable> table;
+    ASSERT_OK(client_->OpenTable(table_name, &table));
+    {
+      const auto& schema = table->schema();
+      ASSERT_EQ(2, schema.num_columns());
+    }
+
+    shared_ptr<KuduSession> session = client_->NewSession();
+    ASSERT_OK(session->SetFlushMode(KuduSession::MANUAL_FLUSH));
+
+    unique_ptr<KuduInsert> insert(table->NewInsert());
+    ASSERT_OK(insert->mutable_row()->SetInt64(0, 42));
+    if (is_null_array_cell) {
+      ASSERT_OK(insert->mutable_row()->SetNull(1));
+    } else {
+      ASSERT_OK(insert->mutable_row()->SetArray<ArrayType>(1, {}, {}));
+    }
+    ASSERT_OK(session->Apply(insert.release()));
+    FlushSessionOrDie(session);
+
+    if (flush_tablet_data) {
+      // Flush data to disk, so it's read from there, not from MRS.
+      const string tablet_id = GetFirstTabletId(table.get());
+      ASSERT_OK(cluster_->FlushTablet(tablet_id));
+    }
+
+    // To read the written data back, use a projection with the index
+    // and the array columns.
+    KuduScanner scanner(table.get());
+    ASSERT_OK(scanner.SetProjectedColumnIndexes({ 0, 1 }));
+    ASSERT_OK(scanner.Open());
+
+    const auto& schema = KuduSchema::ToSchema(scanner.GetProjectionSchema());
+    size_t row_stride = ContiguousRowHelper::row_size(schema);
+
+    size_t row_count = 0;
+    KuduScanBatch batch;
+    while (scanner.HasMoreRows()) {
+      ASSERT_OK(scanner.NextBatch(&batch));
+      for (const KuduScanBatch::RowPtr& row : batch) {
+        int64_t key_val;
+        ASSERT_OK(row.GetInt64(0, &key_val));
+        ASSERT_EQ(42, key_val);
+
+        if (is_null_array_cell) {
+          ASSERT_TRUE(row->IsNull(1));
+        } else {
+          ASSERT_FALSE(row->IsNull(1));
+        }
+
+        // Retrive array data using typed getters.
+        {
+          vector<ElementType> data;
+          vector<bool> validity;
+          if (is_null_array_cell) {
+            const auto s = row->GetArray<TypeTraits<T>>(1, &data, &validity);
+            ASSERT_TRUE(s.IsNotFound()) << s.ToString();
+            ASSERT_STR_CONTAINS(s.ToString(), "column is NULL");
+          } else {
+            ASSERT_OK(row->GetArray<TypeTraits<T>>(1, &data, &validity));
+            ASSERT_TRUE(data.empty());
+            ASSERT_TRUE(validity.empty());
+          }
+        }
+
+        // Retrieve raw array cell data. From the raw data access point,
+        // both empty and null array cells look similar when accessing
+        // the data via KuduArrayCellView: both the KuduArrayCellView::data()
+        // and KuduArrayCellView::not_null_bitmap() should return nullptr.
+        {
+          const void* cell_raw = row.cell(1);
+          ASSERT_NE(nullptr, cell_raw);
+
+          const uint8_t* row_data =
+              batch.direct_data().data() + row_stride * row_count;
+          const Slice* raw_array_cell = reinterpret_cast<const Slice*>(
+              ContiguousRowHelper::cell_ptr(schema, row_data, 1));
+
+          KuduArrayCellView view(raw_array_cell->data(), 
raw_array_cell->size());
+          ASSERT_OK(view.Init());
+          ASSERT_TRUE(view.empty());
+          ASSERT_EQ(0, view.elem_num());
+          const auto* raw_values = view.data(FromInternalDataType(T), {});
+          ASSERT_EQ(nullptr, raw_values);
+          const auto* validity_bitmap = view.not_null_bitmap();
+          ASSERT_EQ(nullptr, validity_bitmap);
+        }
+        ++row_count;
+      }
+    }
+    ASSERT_EQ(1, row_count);
+    ASSERT_OK(client_->DeleteTable(table_name));
+  }
+
+  void TestEmptyAndNullArrays() {
+    const auto elem_type = std::get<0>(GetParam());
+    switch (elem_type) {
+      case DataType::BOOL:
+        return TestEmptyAndNullArraysImpl<DataType::BOOL>();
+      case DataType::INT8:
+        return TestEmptyAndNullArraysImpl<DataType::INT8>();
+      case DataType::INT16:
+        return TestEmptyAndNullArraysImpl<DataType::INT16>();
+      case DataType::INT32:
+        return TestEmptyAndNullArraysImpl<DataType::INT32>();
+      case DataType::INT64:
+        return TestEmptyAndNullArraysImpl<DataType::INT64>();
+      case DataType::BINARY:
+        return TestEmptyAndNullArraysImpl<DataType::BINARY>();
+      case DataType::STRING:
+        return TestEmptyAndNullArraysImpl<DataType::STRING>();
+      case DataType::FLOAT:
+        return TestEmptyAndNullArraysImpl<DataType::FLOAT>();
+      case DataType::DOUBLE:
+        return TestEmptyAndNullArraysImpl<DataType::DOUBLE>();
+      case DataType::UNIXTIME_MICROS:
+        return TestEmptyAndNullArraysImpl<DataType::UNIXTIME_MICROS>();
+      case DataType::DATE:
+        return TestEmptyAndNullArraysImpl<DataType::DATE>();
+      case DataType::DECIMAL32:
+        return TestEmptyAndNullArraysImpl<DataType::DECIMAL32>();
+      case DataType::DECIMAL64:
+        return TestEmptyAndNullArraysImpl<DataType::DECIMAL64>();
+      case DataType::VARCHAR:
+        return TestEmptyAndNullArraysImpl<DataType::VARCHAR>();
+      default:
+        FAIL() << "unexpected array element type " << DataType_Name(elem_type);
+    }
+  }
+};
+INSTANTIATE_TEST_SUITE_P(Params, ArrayColumnParamTest,
+                         testing::Combine(testing::ValuesIn(kArrayElemTypes),
+                                          testing::Bool(),  // is null
+                                          testing::Bool()));// flush/no flush
+
+TEST_P(ArrayColumnParamTest, EmptyAndNullArrays) {
+  NO_FATALS(TestEmptyAndNullArrays());
+}
+
 } // namespace client
 } // namespace kudu
diff --git a/src/kudu/client/scan_batch.cc b/src/kudu/client/scan_batch.cc
index 9eec5fab3..6cbdac6cd 100644
--- a/src/kudu/client/scan_batch.cc
+++ b/src/kudu/client/scan_batch.cc
@@ -18,9 +18,10 @@
 #include "kudu/client/scan_batch.h"
 
 #include <algorithm>
-#include <iterator>
 #include <cstring>
+#include <iterator>
 #include <string>
+#include <type_traits>
 #include <vector>
 
 #include <glog/logging.h>
@@ -304,54 +305,35 @@ Status KuduScanBatch::RowPtr::GetArray(int col_idx,
   ArrayCellMetadataView view(cell_data->data(), cell_data->size());
   RETURN_NOT_OK(view.Init());
 
+  const size_t elem_num = view.elem_num();
   if (data_out) {
-    data_out->resize(view.elem_num());
+    data_out->resize(elem_num);
     if (!view.empty()) {
       const uint8_t* data_raw = view.data_as(T::type);
       DCHECK(data_raw);
-      memcpy(data_out->data(), data_raw, view.elem_num() * sizeof(typename 
T::cpp_type));
+      if constexpr (std::is_same<T, TypeTraits<BOOL>>::value) {
+        // Since std::vector<bool> isn't a standard vector container, the 
data()
+        // accessor isn't available and copying the data using memcpy() isn't
+        // feasible. So, copying the data element by element: it's not as
+        // performant as memcpy(), but it works.
+        data_out->clear();
+        data_out->reserve(elem_num);
+        std::copy(data_raw, data_raw + elem_num, 
std::back_inserter(*data_out));
+      } else {
+        memcpy(data_out->data(), data_raw, elem_num * sizeof(typename 
T::cpp_type));
+      }
     }
   }
   if (validity_out) {
-    validity_out->resize(view.elem_num());
+    validity_out->resize(elem_num);
     if (!view.empty()) {
-      *validity_out = BitmapToVector(view.not_null_bitmap(), view.elem_num());
+      DCHECK(view.not_null_bitmap());
+      *validity_out = BitmapToVector(view.not_null_bitmap(), elem_num);
     }
   }
   return Status::OK();
 }
 
-// Since std::vector<bool> isn't a standard container, the data() accessor
-// isn't available and copying the data requires an alternative approach.
-template<>
-Status KuduScanBatch::RowPtr::GetArray<TypeTraits<BOOL>>(
-    int col_idx,
-    vector<bool>* data_out,
-    vector<bool>* validity) const {
-  const ColumnSchema& col = schema_->column(col_idx);
-  RETURN_NOT_OK(ArrayValidation(col, TypeTraits<BOOL>::name()));
-  if (PREDICT_FALSE(col.is_nullable() && IsNull(col_idx))) {
-    return Status::NotFound("column is NULL");
-  }
-  const Slice* cell_data = reinterpret_cast<const Slice*>(
-      row_data_ + schema_->column_offset(col_idx));
-  ArrayCellMetadataView view(cell_data->data(), cell_data->size());
-  RETURN_NOT_OK(view.Init());
-
-  if (data_out) {
-    const size_t elem_num = view.elem_num();
-    data_out->clear();
-    data_out->reserve(elem_num);
-    const uint8_t* data_raw = view.data_as(BOOL);
-    DCHECK(data_raw);
-    std::copy(data_raw, data_raw + elem_num, std::back_inserter(*data_out));
-  }
-  if (validity) {
-    *validity = BitmapToVector(view.not_null_bitmap(), view.elem_num());
-  }
-  return Status::OK();
-}
-
 Status KuduScanBatch::RowPtr::GetArrayBool(int col_idx,
                                            vector<bool>* data,
                                            vector<bool>* validity) const {
@@ -525,6 +507,150 @@ Status KuduScanBatch::RowPtr::Get<TypeTraits<DECIMAL64> 
>(int col_idx, int64_t*
 template
 Status KuduScanBatch::RowPtr::Get<TypeTraits<DECIMAL128> >(int col_idx, 
int128_t* val) const;
 
+
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<BOOL>>(
+    const Slice& col_name,
+    vector<bool>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT8>>(
+    const Slice& col_name,
+    vector<int8_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT16>>(
+    const Slice& col_name,
+    vector<int16_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT32>>(
+    const Slice& col_name,
+    vector<int32_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT64>>(
+    const Slice& col_name,
+    vector<int64_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<UNIXTIME_MICROS>>(
+    const Slice& col_name,
+    vector<int64_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DATE>>(
+    const Slice& col_name,
+    vector<int32_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<FLOAT>>(
+    const Slice& col_name,
+    vector<float>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DOUBLE>>(
+    const Slice& col_name,
+    vector<double>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<STRING>>(
+    const Slice& col_name,
+    vector<Slice>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<BINARY>>(
+    const Slice& col_name,
+    vector<Slice>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<VARCHAR>>(
+    const Slice& col_name,
+    vector<Slice>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DECIMAL32>>(
+    const Slice& col_name,
+    vector<int32_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DECIMAL64>>(
+    const Slice& col_name,
+    vector<int64_t>* data,
+    vector<bool>* validity) const;
+
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<BOOL>>(
+    int col_idx,
+    vector<bool>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT8>>(
+    int col_idx,
+    vector<int8_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT16>>(
+    int col_idx,
+    vector<int16_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT32>>(
+    int col_idx,
+    vector<int32_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<INT64>>(
+    int col_idx,
+    vector<int64_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<UNIXTIME_MICROS>>(
+    int col_idx,
+    vector<int64_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DATE>>(
+    int col_idx,
+    vector<int32_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<FLOAT>>(
+    int col_idx,
+    vector<float>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DOUBLE>>(
+    int col_idx,
+    vector<double>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<STRING>>(
+    int col_idx,
+    vector<Slice>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<BINARY>>(
+    int col_idx,
+    vector<Slice>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<VARCHAR>>(
+    int col_idx,
+    vector<Slice>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DECIMAL32>>(
+    int col_idx,
+    vector<int32_t>* data,
+    vector<bool>* validity) const;
+template
+Status KuduScanBatch::RowPtr::GetArray<TypeTraits<DECIMAL64>>(
+    int col_idx,
+    vector<int64_t>* data,
+    vector<bool>* validity) const;
+
+
 Status KuduScanBatch::RowPtr::GetUnscaledDecimal(int col_idx, int128_t* val) 
const {
   const ColumnSchema& col = schema_->column(col_idx);
   const DataType col_type = col.type_info()->type();
diff --git a/src/kudu/client/scan_batch.h b/src/kudu/client/scan_batch.h
index 092a2ec07..2a5dcb5fa 100644
--- a/src/kudu/client/scan_batch.h
+++ b/src/kudu/client/scan_batch.h
@@ -442,6 +442,7 @@ class KUDU_EXPORT KuduScanBatch::RowPtr {
   friend class tools::TableScanner;
   template<typename KeyTypeWrapper> friend struct SliceKeysTestSetup;
   template<typename KeyTypeWrapper> friend struct IntKeysTestSetup;
+  friend class ArrayColumnParamTest;
 
   // Only invoked by KuduScanner.
   RowPtr(const Schema* schema,
diff --git a/src/kudu/common/partial_row.h b/src/kudu/common/partial_row.h
index 01bf10502..84d6504b8 100644
--- a/src/kudu/common/partial_row.h
+++ b/src/kudu/common/partial_row.h
@@ -43,6 +43,7 @@
 namespace kudu {
 class ColumnSchema;
 namespace client {
+class ArrayColumnParamTest;
 class ClientTest_TestProjectionPredicatesFuzz_Test;
 class KuduWriteOperation;
 namespace internal {
@@ -741,6 +742,7 @@ class KUDU_EXPORT KuduPartialRow {
   const Schema* schema() const { return schema_; }
 
  private:
+  friend class client::ArrayColumnParamTest;
   friend class client::KuduWriteOperation;  // for row_data_
   friend class client::internal::WriteRpc;  // for row_data_
   friend class tools::PartialRow;           // for Set<T>(), SetArray<T>()

Reply via email to