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 9e3901509 KUDU-1261 introduce ARRAY_1D_COLUMN_TYPE feature for masters
9e3901509 is described below
commit 9e39015095f5a8a3f33bc87fe19617c7d3caf410
Author: Alexey Serbin <[email protected]>
AuthorDate: Thu Oct 16 22:22:40 2025 -0700
KUDU-1261 introduce ARRAY_1D_COLUMN_TYPE feature for masters
This changelist introduces a new feature flag ARRAY_1D_COLUMN_TYPE
for Kudu masters to reflect whether one-dimensional array columns
are supported by the system catalog. Also, the C++ client code
is updated to require the feature flag at the server side when sending
requests to create a table with array type columns or adding an array
type column into already existing tables.
With this patch, attempts to create a new table with an array column
using a kudu-master without the necessary functionality (e.g., bits
compiled from the 1.18.x branch) result in proper behavior, and a
proper, actionable error is reported by the client:
Not implemented: Error creating table array_table on the master: cluster
does not support CreateTable with feature(s) ARRAY_1D_COLUMN_TYPE
Without this patch, an attempt to create the same table would result
in an error as well, but it's quite cryptic and not actionable, e.g.:
Remote error: Error creating table array_table on the master: Invalid
argument: invalid parameter for call kudu.master.MasterService.CreateTable:
missing fields: schema.columns[3].type, schema.columns[4].type
I'm planning to post a separate patch to update the Java client as well.
Change-Id: I61ab0d7446409ad924454d2d272bb02512bdffc8
Reviewed-on: http://gerrit.cloudera.org:8080/23556
Reviewed-by: Abhishek Chennaka <[email protected]>
Tested-by: Alexey Serbin <[email protected]>
---
src/kudu/client/CMakeLists.txt | 1 +
src/kudu/client/client-internal.cc | 17 +++-
src/kudu/client/client-internal.h | 6 +-
src/kudu/client/client.cc | 10 +-
src/kudu/client/service-features-test.cc | 150 ++++++++++++++++++++++++++++++
src/kudu/client/table_alterer-internal.cc | 6 +-
src/kudu/client/table_alterer-internal.h | 5 +-
src/kudu/common/schema.cc | 7 ++
src/kudu/common/schema.h | 13 +++
src/kudu/master/master.proto | 3 +
src/kudu/master/master_service.cc | 8 ++
11 files changed, 215 insertions(+), 11 deletions(-)
diff --git a/src/kudu/client/CMakeLists.txt b/src/kudu/client/CMakeLists.txt
index 7255d7c17..a5653289d 100644
--- a/src/kudu/client/CMakeLists.txt
+++ b/src/kudu/client/CMakeLists.txt
@@ -293,3 +293,4 @@ ADD_KUDU_TEST(client-unittest)
ADD_KUDU_TEST(flex_partitioning_client-test)
ADD_KUDU_TEST(predicate-test NUM_SHARDS 4)
ADD_KUDU_TEST(scan_token-test PROCESSORS 2)
+ADD_KUDU_TEST(service-features-test PROCESSORS 2)
diff --git a/src/kudu/client/client-internal.cc
b/src/kudu/client/client-internal.cc
index fbd6048b1..82deaef6b 100644
--- a/src/kudu/client/client-internal.cc
+++ b/src/kudu/client/client-internal.cc
@@ -36,7 +36,6 @@
#include <boost/container/vector.hpp>
#include <gflags/gflags_declare.h>
#include <glog/logging.h>
-#include <google/protobuf/stubs/common.h>
#include "kudu/client/authz_token_cache.h"
#include "kudu/client/master_proxy_rpc.h"
@@ -328,7 +327,8 @@ Status KuduClient::Data::CreateTable(KuduClient* client,
bool has_range_partition_bounds,
bool has_range_specific_hash_schema,
bool has_immutable_column_schema,
- bool has_auto_incrementing_column) {
+ bool has_auto_incrementing_column,
+ bool has_nested_columns) {
vector<uint32_t> features;
if (has_range_partition_bounds) {
features.push_back(MasterFeatures::RANGE_PARTITION_BOUNDS);
@@ -342,6 +342,13 @@ Status KuduClient::Data::CreateTable(KuduClient* client,
if (has_auto_incrementing_column) {
features.push_back(MasterFeatures::AUTO_INCREMENTING_COLUMN);
}
+ if (has_nested_columns) {
+ // As of time of writing, a nested column can only be of 1D array type.
+ // When more nested types are supported, the 'has_nested_columns' parameter
+ // needs to become an integer where different bits stands for particular
+ // features required at the server-side.
+ features.push_back(MasterFeatures::ARRAY_1D_COLUMN_TYPE);
+ }
Synchronizer sync;
AsyncLeaderMasterRpc<CreateTableRequestPB, CreateTableResponsePB> rpc(
deadline, client, BackoffType::EXPONENTIAL, req, resp,
@@ -434,7 +441,8 @@ Status KuduClient::Data::AlterTable(KuduClient* client,
const MonoTime& deadline,
bool has_add_drop_partition,
bool adding_range_with_custom_hash_schema,
- bool has_immutable_column_schema) {
+ bool has_immutable_column_schema,
+ bool has_nested_columns) {
vector<uint32_t> required_feature_flags;
if (has_add_drop_partition) {
required_feature_flags.push_back(MasterFeatures::ADD_DROP_RANGE_PARTITIONS);
@@ -445,6 +453,9 @@ Status KuduClient::Data::AlterTable(KuduClient* client,
if (has_immutable_column_schema) {
required_feature_flags.push_back(MasterFeatures::IMMUTABLE_COLUMN_ATTRIBUTE);
}
+ if (has_nested_columns) {
+ required_feature_flags.push_back(MasterFeatures::ARRAY_1D_COLUMN_TYPE);
+ }
Synchronizer sync;
AsyncLeaderMasterRpc<AlterTableRequestPB, AlterTableResponsePB> rpc(
deadline, client, BackoffType::EXPONENTIAL, req, resp,
diff --git a/src/kudu/client/client-internal.h
b/src/kudu/client/client-internal.h
index 2bf217794..ebac4bcc3 100644
--- a/src/kudu/client/client-internal.h
+++ b/src/kudu/client/client-internal.h
@@ -109,7 +109,8 @@ class KuduClient::Data {
bool has_range_partition_bounds,
bool has_range_specific_hash_schema,
bool has_immutable_column_schema,
- bool has_auto_incrementing_column);
+ bool has_auto_incrementing_column,
+ bool has_nested_columns);
static Status IsCreateTableInProgress(KuduClient* client,
master::TableIdentifierPB table,
@@ -137,7 +138,8 @@ class KuduClient::Data {
const MonoTime& deadline,
bool has_add_drop_partition,
bool adding_range_with_custom_hash_schema,
- bool has_immutable_column_schema);
+ bool has_immutable_column_schema,
+ bool has_nested_columns);
static Status IsAlterTableInProgress(KuduClient* client,
master::TableIdentifierPB table,
diff --git a/src/kudu/client/client.cc b/src/kudu/client/client.cc
index 5c28a401c..92d8a7ac5 100644
--- a/src/kudu/client/client.cc
+++ b/src/kudu/client/client.cc
@@ -31,7 +31,6 @@
#include <vector>
#include <glog/logging.h>
-#include <google/protobuf/stubs/common.h>
#include "kudu/client/callbacks.h"
#include "kudu/client/client-internal.h"
@@ -1125,7 +1124,6 @@ Status KuduTableCreator::Create() {
break;
}
}
- bool has_auto_incrementing_column =
data_->schema_->schema_->has_auto_incrementing();
if (data_->table_type_) {
req.set_table_type(*data_->table_type_);
@@ -1138,6 +1136,8 @@ Status KuduTableCreator::Create() {
deadline += data_->client_->default_admin_operation_timeout();
}
+ const bool has_auto_incrementing_column =
data_->schema_->schema_->has_auto_incrementing();
+ const bool has_nested_columns =
data_->schema_->schema_->has_nested_columns();
CreateTableResponsePB resp;
RETURN_NOT_OK_PREPEND(
data_->client_->data_->CreateTable(data_->client_,
@@ -1147,7 +1147,8 @@ Status KuduTableCreator::Create() {
!data_->range_partitions_.empty(),
has_range_with_custom_hash_schema,
has_immutable_column_schema,
- has_auto_incrementing_column),
+ has_auto_incrementing_column,
+ has_nested_columns),
Substitute("Error creating table $0 on the master", data_->table_name_));
// Spin until the table is fully created, if requested.
if (data_->wait_) {
@@ -1778,7 +1779,8 @@ Status KuduTableAlterer::Alter() {
data_->client_, req, &resp, deadline,
data_->has_alter_partitioning_steps,
data_->adding_range_with_custom_hash_schema,
- has_immutable_column_schema));
+ has_immutable_column_schema,
+ data_->has_nested_column_update_));
if (data_->has_alter_partitioning_steps) {
// If the table partitions change, clear the local meta cache so that the
diff --git a/src/kudu/client/service-features-test.cc
b/src/kudu/client/service-features-test.cc
new file mode 100644
index 000000000..acb878cc2
--- /dev/null
+++ b/src/kudu/client/service-features-test.cc
@@ -0,0 +1,150 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <gflags/gflags_declare.h>
+#include <gtest/gtest.h>
+
+#include "kudu/client/client.h"
+#include "kudu/client/schema.h"
+#include "kudu/client/shared_ptr.h" // IWYU pragma: keep
+#include "kudu/common/common.pb.h"
+#include "kudu/common/schema.h"
+#include "kudu/mini-cluster/internal_mini_cluster.h"
+#include "kudu/util/status.h"
+#include "kudu/util/test_macros.h"
+#include "kudu/util/test_util.h"
+
+DECLARE_bool(master_support_1d_array_columns);
+
+using kudu::client::sp::shared_ptr;
+using kudu::cluster::InternalMiniCluster;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace kudu {
+namespace client {
+
+class ServiceFeaturesITest : public KuduTest {
+ public:
+ void SetUp() override {
+ // Set up the mini cluster
+ cluster_.reset(new InternalMiniCluster(env_, {}));
+ ASSERT_OK(cluster_->Start());
+ ASSERT_OK(cluster_->CreateClient(nullptr, &client_));
+ }
+
+ protected:
+ unique_ptr<InternalMiniCluster> cluster_;
+ shared_ptr<KuduClient> client_;
+};
+
+// This test scenario verifies that Kudu C++ client of this version properly
+// reports an error upon failed DDL requests involving tables with array type
+// columns. If such DDL requests are run against Kudu master that doesn't
+// support array type columns, and the behavior is as expected and the error
+// message output by the client is actionable.
+TEST_F(ServiceFeaturesITest, ArrayColumnSupportMaster) {
+ constexpr const char* const kTableName = "array_columns_support_m";
+ const Schema array_col_schema({
+ ColumnSchema("key", INT32),
+ ColumnSchemaBuilder().name("val").type(INT32).array(true).nullable(true)
+ }, 1);
+
+ // Make master not declaring its ARRAY_1D_COLUMN_TYPE feature.
+ FLAGS_master_support_1d_array_columns = false;
+
+ unique_ptr<KuduTableCreator> table_creator(client_->NewTableCreator());
+ KuduSchema schema(KuduSchema::FromSchema(array_col_schema));
+ table_creator->table_name(kTableName)
+ .schema(&schema)
+ .add_hash_partitions({ "key" }, 2)
+ .num_replicas(1);
+ const auto s = table_creator->Create();
+ ASSERT_TRUE(s.IsNotSupported()) << s.ToString();
+ ASSERT_STR_CONTAINS(s.ToString(),
+ "Error creating table array_columns_support_m on the master: cluster "
+ "does not support CreateTable with feature(s) ARRAY_1D_COLUMN_TYPE");
+
+ // If master has ARRAY_1D_COLUMN_TYPE feature, it's possible to create tables
+ // with array columns in the system catalog.
+ FLAGS_master_support_1d_array_columns = true;
+ ASSERT_OK(table_creator->Create());
+
+ {
+ unique_ptr<KuduTableAlterer> alt(client_->NewTableAlterer(kTableName));
+ alt->AddColumn("c0")->NestedType(
+ KuduColumnSchema::KuduNestedTypeDescriptor(
+
KuduColumnSchema::KuduArrayTypeDescriptor(KuduColumnSchema::INT8)));
+ ASSERT_OK(alt->Alter());
+ }
+
+ // Below are unlikely sub-scenarios specific to disabling array type column
+ // support in a Kudu master (i.e. --setting
master_support_1d_array_columns=false)
+ // after a table with array column(s) has been created during prior run of
+ // the Kudu master while support for array type columns was enabled.
+ FLAGS_master_support_1d_array_columns = false;
+
+ // It's still possible to rename and drop the column, update the column's
+ // comment, etc. However, it's not possible to add a new array type column.
+ {
+ unique_ptr<KuduTableAlterer> alt(client_->NewTableAlterer(kTableName));
+ alt->AlterColumn("c0")->RenameTo("array0");
+ ASSERT_OK(alt->Alter());
+ }
+ {
+ unique_ptr<KuduTableAlterer> alt(client_->NewTableAlterer(kTableName));
+ alt->AlterColumn("array0")->Comment("comment");
+ ASSERT_OK(alt->Alter());
+ }
+ {
+ unique_ptr<KuduTableAlterer> alt(client_->NewTableAlterer(kTableName));
+ alt->DropColumn("array0");
+ ASSERT_OK(alt->Alter());
+ }
+ {
+ unique_ptr<KuduTableAlterer> alt(client_->NewTableAlterer(kTableName));
+ alt->AddColumn("c0")->NestedType(
+ KuduColumnSchema::KuduNestedTypeDescriptor(
+
KuduColumnSchema::KuduArrayTypeDescriptor(KuduColumnSchema::INT16)));
+ const auto s = alt->Alter();
+ ASSERT_TRUE(s.IsNotSupported()) << s.ToString();
+ ASSERT_STR_CONTAINS(s.ToString(),
+ "cluster does not support AlterTable with feature(s)
ARRAY_1D_COLUMN_TYPE");
+ }
+ {
+ // It should be possible to open the table.
+ shared_ptr<KuduTable> table;
+ ASSERT_OK(client_->OpenTable(kTableName, &table));
+ }
+ // It should be possible to drop the table.
+ ASSERT_OK(client_->DeleteTable(kTableName));
+
+ {
+ vector<string> tables;
+ ASSERT_OK(client_->ListTables(&tables));
+ ASSERT_TRUE(tables.empty());
+ }
+}
+
+} // namespace client
+} // namespace kudu
diff --git a/src/kudu/client/table_alterer-internal.cc
b/src/kudu/client/table_alterer-internal.cc
index cd02bb61c..6ce8302b0 100644
--- a/src/kudu/client/table_alterer-internal.cc
+++ b/src/kudu/client/table_alterer-internal.cc
@@ -24,7 +24,6 @@
#include <utility>
#include <glog/logging.h>
-#include <google/protobuf/stubs/common.h>
#include "kudu/client/schema-internal.h"
#include "kudu/client/schema.h"
@@ -33,6 +32,7 @@
#include "kudu/common/row_operations.h"
#include "kudu/common/row_operations.pb.h"
#include "kudu/common/schema.h"
+#include "kudu/common/types.h"
#include "kudu/common/wire_protocol.h"
#include "kudu/master/master.pb.h"
@@ -46,6 +46,7 @@ KuduTableAlterer::Data::Data(KuduClient* client, string name)
: client_(client),
table_name_(std::move(name)),
wait_(true),
+ has_nested_column_update_(false),
schema_(nullptr) {
}
@@ -121,6 +122,9 @@ Status
KuduTableAlterer::Data::ToRequest(AlterTableRequestPB* req) {
ColumnSchemaToPB(*col.col_,
pb_step->mutable_add_column()->mutable_schema(),
SCHEMA_PB_WITHOUT_WRITE_DEFAULT);
+ if (col.col_->type_info()->type() == DataType::NESTED) {
+ has_nested_column_update_ = true;
+ }
break;
}
case AlterTableRequestPB::DROP_COLUMN:
diff --git a/src/kudu/client/table_alterer-internal.h
b/src/kudu/client/table_alterer-internal.h
index b186fa890..a715705c6 100644
--- a/src/kudu/client/table_alterer-internal.h
+++ b/src/kudu/client/table_alterer-internal.h
@@ -37,7 +37,7 @@ namespace client {
class KuduColumnSpec;
-class KuduTableAlterer::Data {
+class KuduTableAlterer::Data final {
public:
Data(KuduClient* client, std::string name);
~Data();
@@ -84,6 +84,9 @@ class KuduTableAlterer::Data {
// Set to true if a new range with custom hash schema is being added.
bool adding_range_with_custom_hash_schema = false;
+ // Set to true if a nested type column is being added or altered.
+ bool has_nested_column_update_ = false;
+
// Schema of add/drop range partition bound rows.
const Schema* schema_;
diff --git a/src/kudu/common/schema.cc b/src/kudu/common/schema.cc
index 9c4132f40..92443f654 100644
--- a/src/kudu/common/schema.cc
+++ b/src/kudu/common/schema.cc
@@ -228,6 +228,7 @@ void Schema::CopyFrom(const Schema& other) {
first_is_deleted_virtual_column_idx_ =
other.first_is_deleted_virtual_column_idx_;
has_nullables_ = other.has_nullables_;
+ has_nested_columns_ = other.has_nested_columns_;
auto_incrementing_col_idx_ = other.auto_incrementing_col_idx_;
}
@@ -241,6 +242,7 @@ Schema::Schema(Schema&& other) noexcept
id_to_index_(std::move(other.id_to_index_)),
first_is_deleted_virtual_column_idx_(other.first_is_deleted_virtual_column_idx_),
has_nullables_(other.has_nullables_),
+ has_nested_columns_(other.has_nested_columns_),
auto_incrementing_col_idx_(other.auto_incrementing_col_idx_) {
}
@@ -254,6 +256,7 @@ Schema& Schema::operator=(Schema&& other) noexcept {
id_to_index_ = std::move(other.id_to_index_);
first_is_deleted_virtual_column_idx_ =
other.first_is_deleted_virtual_column_idx_;
has_nullables_ = other.has_nullables_;
+ has_nested_columns_ = other.has_nested_columns_;
name_to_index_ = std::move(other.name_to_index_);
auto_incrementing_col_idx_ = other.auto_incrementing_col_idx_;
}
@@ -299,6 +302,7 @@ Status Schema::Reset(vector<ColumnSchema> cols,
}
}
+ has_nested_columns_ = false;
auto_incrementing_col_idx_ = auto_incrementing_col_idx;
// Calculate the offset of each column in the row format.
col_offsets_.clear();
@@ -325,6 +329,9 @@ Status Schema::Reset(vector<ColumnSchema> cols,
col_offsets_.push_back(off);
off += col.type_info()->size();
+ if (col.type_info()->type() == DataType::NESTED) {
+ has_nested_columns_ = true;
+ }
}
// Add an extra element on the end for the total
diff --git a/src/kudu/common/schema.h b/src/kudu/common/schema.h
index be61345fe..14b744d78 100644
--- a/src/kudu/common/schema.h
+++ b/src/kudu/common/schema.h
@@ -886,6 +886,16 @@ class Schema {
return has_nullables_;
}
+ // Returns true if the schema contains at least one nested column.
+ //
+ // NOTE: As of time of writing, a nested column can only be of 1D array type.
+ // When more nested types are supported, the 'has_nested_columns' parameter
+ // needs to become an integer where different bits stands for particular
+ // features required at the server-side.
+ bool has_nested_columns() const {
+ return has_nested_columns_;
+ }
+
// Returns true if the specified column (by name) is a key
bool is_key_column(const StringPiece col_name) const {
return is_key_column(find_column(col_name));
@@ -1282,6 +1292,9 @@ class Schema {
// Cached indicator whether any columns are nullable.
bool has_nullables_;
+ // Cached indicator whether any columns are of array type.
+ bool has_nested_columns_ = false;
+
// Cached index of the auto-incrementing column, or kColumnNotFound if no
// such column exists in the schema.
int auto_incrementing_col_idx_;
diff --git a/src/kudu/master/master.proto b/src/kudu/master/master.proto
index f8ff85d3e..12b74f4d6 100644
--- a/src/kudu/master/master.proto
+++ b/src/kudu/master/master.proto
@@ -1214,6 +1214,9 @@ enum MasterFeatures {
IMMUTABLE_COLUMN_ATTRIBUTE = 10;
// Whether master supports auto incrementing column.
AUTO_INCREMENTING_COLUMN = 11;
+ // Whether the system catalog supports tables with one-dimensional array
+ // type columns.
+ ARRAY_1D_COLUMN_TYPE = 12;
}
service MasterService {
diff --git a/src/kudu/master/master_service.cc
b/src/kudu/master/master_service.cc
index f6dbe55cf..e2af71714 100644
--- a/src/kudu/master/master_service.cc
+++ b/src/kudu/master/master_service.cc
@@ -125,6 +125,12 @@ TAG_FLAG(master_support_auto_incrementing_column, unsafe);
TAG_FLAG(master_support_auto_incrementing_column, experimental);
TAG_FLAG(master_support_auto_incrementing_column, runtime);
+DEFINE_bool(master_support_1d_array_columns, true,
+ "Whether the system catalog supports creating tables with "
+ "one-dimensional columns");
+TAG_FLAG(master_support_1d_array_columns, hidden);
+TAG_FLAG(master_support_1d_array_columns, runtime);
+
using google::protobuf::Message;
using kudu::consensus::ReplicaManagementInfoPB;
using kudu::pb_util::SecureDebugString;
@@ -991,6 +997,8 @@ bool MasterServiceImpl::SupportsFeature(uint32_t feature)
const {
return FLAGS_master_support_immutable_column_attribute;
case MasterFeatures::AUTO_INCREMENTING_COLUMN:
return FLAGS_master_support_auto_incrementing_column;
+ case MasterFeatures::ARRAY_1D_COLUMN_TYPE:
+ return FLAGS_master_support_1d_array_columns;
default:
return false;
}