blob: 9dc7472af5f41be9ea8bcd70e8bd07824eccf488 [file]
/*
* 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 "paimon/core/schema/schema_validation.h"
#include <map>
#include "arrow/api.h"
#include "gtest/gtest.h"
#include "paimon/common/data/blob_utils.h"
#include "paimon/core/schema/table_schema.h"
#include "paimon/defs.h"
#include "paimon/testing/utils/testharness.h"
namespace paimon::test {
TEST(SchemaValidationTest, TestSimple) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
TEST(SchemaValidationTest, TestRowTracking) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {
{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
TEST(SchemaValidationTest, TestWithBlobField) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
std::shared_ptr<arrow::Field> f3 = BlobUtils::ToArrowField("f3", false);
std::shared_ptr<arrow::Field> f4 = BlobUtils::ToArrowField("f4", false);
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_FIELD, "f3"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
{
arrow::FieldVector fields = {f0, f1, f2, f3, f4};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_FIELD, "f3,f4"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
{
arrow::FieldVector fields = {f0, f1, f2, f3, f4};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {
{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_DESCRIPTOR_FIELD, "f3"},
{Options::BLOB_VIEW_FIELD, "f4"},
{Options::BLOB_EXTERNAL_STORAGE_FIELD, "f3"},
{Options::BLOB_EXTERNAL_STORAGE_PATH, "FILE:///tmp/blob_external_storage/"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_DESCRIPTOR_FIELD, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"Field 'f0' in 'blob-descriptor-field' must be a BLOB field in table schema.");
}
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_DESCRIPTOR_FIELD, "f3"},
{Options::BLOB_VIEW_FIELD, "f3"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"Field 'f3' in 'blob-view-field' can not also be in 'blob-descriptor-field'.");
}
{
arrow::FieldVector fields = {f0, f1, f2, f3, f4};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {
{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_DESCRIPTOR_FIELD, "f3"},
{Options::BLOB_EXTERNAL_STORAGE_FIELD, "f4"},
{Options::BLOB_EXTERNAL_STORAGE_PATH, "FILE:///tmp/blob_external_storage/"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"Field 'f4' in 'blob-external-storage-field' must also be in 'blob-descriptor-field'.");
}
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_DESCRIPTOR_FIELD, "f3"},
{Options::BLOB_EXTERNAL_STORAGE_FIELD, "f3"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"'blob-external-storage-path' must be set when "
"'blob-external-storage-field' is configured.");
}
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "false"},
{Options::BLOB_FIELD, "f3"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"Data evolution config must be enabled for table with BLOB type column.");
}
{
arrow::FieldVector fields = {f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_FIELD, "f3"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Table with BLOB type column must have other normal columns.");
}
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_FIELD, "non-exist"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Get field non-exist failed: not exist in table schema");
}
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_FIELD, "f3,f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Field 'f0' in 'blob-field' must be a BLOB field in table schema.");
}
{
arrow::FieldVector fields = {f0, f1, f2, f3};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f3"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::BLOB_FIELD, "f3"}};
ASSERT_OK_AND_ASSIGN(auto core_options, CoreOptions::FromMap(options));
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateRowTracking(*table_schema, core_options),
"Blob field f3 cannot be a partition key.");
}
}
TEST(SchemaValidationTest, TestDuplicateField) {
auto f0 = arrow::field("f0", arrow::map(arrow::utf8(), arrow::int32()));
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"}};
{
// duplicate primary keys
std::vector<std::string> dup_primary_keys = {"f0", "f1", "f1"};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys,
dup_primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"primary key [f0, f1, f1] must not contain duplicate fields. Found: [f1]");
}
{
// duplicate partition keys
std::vector<std::string> dup_partition_keys = {"f1", "f1"};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, dup_partition_keys,
primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"partition key [f1, f1] must not contain duplicate fields. Found: [f1]");
}
{
// duplicate bucket keys
std::map<std::string, std::string> dup_options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0,f0"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys,
primary_keys, dup_options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"bucket key [f0, f0] must not contain duplicate fields. Found: [f0]");
}
}
TEST(SchemaValidationTest, TestNonExistField) {
auto f0 = arrow::field("f0", arrow::map(arrow::utf8(), arrow::int32()));
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"}};
{
// non-exist primary keys
std::vector<std::string> non_exist_primary_keys = {"f0", "f1", "non-exist"};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys,
non_exist_primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
R"(Table column ["f0", "f1", "f2"] should include all primary key constraint ["f0", "f1", "non-exist"])");
}
{
// non-exist partition keys
std::vector<std::string> non_exist_partition_keys = {"f1", "non-exist"};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, non_exist_partition_keys,
primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
R"(Table column ["f0", "f1", "f2"] should include all partition fields ["f1", "non-exist"])");
}
}
TEST(SchemaValidationTest, NonPrimitivePrimaryKeyList) {
auto value_field = arrow::field("values", arrow::int32());
auto f0 = arrow::field("f0", arrow::list(value_field));
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"field f0 is unsupported");
}
TEST(SchemaValidationTest, NonPrimitivePrimaryKeyMap) {
auto f0 = arrow::field("f0", arrow::map(arrow::utf8(), arrow::int32()));
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"field f0 is unsupported");
}
TEST(SchemaValidationTest, NonPrimitivePartitionKeyStruct) {
auto f0 = arrow::field("f0", arrow::utf8());
auto child1 = arrow::field("inner1", arrow::int32());
auto child2 = arrow::field("inner2", arrow::float64());
auto f1 = arrow::field("f1", arrow::struct_({child1, child2}));
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"field f1 is unsupported");
}
TEST(SchemaValidationTest, TestSpecificPartitionKey) {
{
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::decimal128(5, 2));
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, {}));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"partition field f1 cannot be TIMESTAMP/DECIMAL/BLOB");
}
{
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::float64());
arrow::FieldVector fields = {f0, f1};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, {}));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"partition field f1 cannot be FLOAT/DOUBLE");
}
}
TEST(SchemaValidationTest, TestComplexPartitionKeyWithBlob) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = BlobUtils::ToArrowField("f1");
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> partition_keys = {"f1"};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, /*primary_keys=*/{}, {}));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"partition field f1 cannot be TIMESTAMP/DECIMAL/BLOB");
}
TEST(SchemaValidationTest, TestDateTypePartitionKey) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::date32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, {}));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
TEST(SchemaValidationTest, ValidateFieldsPrefix) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
{
std::map<std::string, std::string> options = {
{Options::BUCKET, "2"}, {Options::BUCKET_KEY, "f0"}, {"fields.f0,f1,f3", "some_value"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"f3 can not be found in table schema.");
}
{
std::map<std::string, std::string> options = {
{Options::BUCKET, "2"}, {Options::BUCKET_KEY, "f0"}, {"fields.f0,f1,f2", "some_value"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
{
std::map<std::string, std::string> options = {
{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{Options::FIELDS_DEFAULT_AGG_FUNC, "some_value"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
{
std::map<std::string, std::string> options = {
{Options::BUCKET, "2"}, {Options::BUCKET_KEY, "f0"}, {"fields.", "f1"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"invalid options key fields.");
}
}
TEST(SchemaValidationTest, ValidateBucket) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
{
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"please specify a bucket number.");
}
{
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "0"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"The number of buckets needs to be greater than 0.");
}
{
std::vector<std::string> primary_keys = {"f0", "f2"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"You should use dynamic bucket (bucket = -1) mode in cross partition update case");
}
{
std::vector<std::string> primary_keys = {};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"You should define a 'bucket-key' for bucketed append mode");
}
{
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{"full-compaction.delta-commits", "2"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys,
/*primary_keys=*/{}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"AppendOnlyTable of unware or dynamic bucket does not support "
"'full-compaction.delta-commits");
}
{
auto f3 = arrow::field("f3", arrow::map(arrow::utf8(), arrow::int32()));
arrow::FieldVector new_fields = {f0, f1, f2, f3};
auto new_schema = arrow::schema(new_fields);
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f3"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, new_schema, /*partition_keys=*/{},
/*primary_keys=*/{}, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"Nested type cannot be in bucket-key, in your table these keys are: f3");
}
}
TEST(SchemaValidationTest, ValidateDeletionVector) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
{
std::map<std::string, std::string> options = {
{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{Options::DELETION_VECTORS_ENABLED, "true"},
{Options::CHANGELOG_PRODUCER, "full-compaction"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"C++ Paimon does not support changelog-producer yet");
}
{
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{Options::DELETION_VECTORS_ENABLED, "true"},
{Options::MERGE_ENGINE, "first-row"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"First row merge engine does not need deletion vectors because there "
"is no deletion of old data in this merge engine.");
}
}
TEST(SchemaValidationTest, ValidateSequenceField) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
{
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{Options::SEQUENCE_FIELD, "f0,f1,f2"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
{
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{Options::SEQUENCE_FIELD, "f0,f1,f3"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"cannot be found in table schema.");
}
{
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{Options::SEQUENCE_FIELD, "f0,f1,f2"},
{Options::MERGE_ENGINE, "first-row"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Do not support using sequence field on FIRST_ROW merge engine.");
}
{
std::map<std::string, std::string> options = {{Options::BUCKET, "-1"},
{Options::SEQUENCE_FIELD, "f0,f1,f2"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{"f1"},
/*primary_keys=*/{"f0", "f2"}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"You cannot use sequence.field in cross partition update case (Primary "
"key constraint 'f0, f2' not including all partition fields 'f1').");
}
}
TEST(SchemaValidationTest, ValidateSequenceGroup) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
{
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{"fields.f0,f1.sequence-group", "f2"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_OK(SchemaValidation::ValidateTableSchema(*table_schema));
}
{
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{"fields.f0,f3.sequence-group", "f2"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Field f3 can not be found in table schema.");
}
{
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{"fields.f0,f1.sequence-group", "f3"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Field f3 can not be found in table schema.");
}
{
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{"fields.f0,f1.sequence-group", "f0,f1"},
{"fields.f2.sequence-group", "f0,f1"}};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"defined repeatedly by multiple groups");
}
{
std::vector<std::string> primary_keys = {"f0", "f1"};
std::vector<std::string> partition_keys = {"f1"};
std::map<std::string, std::string> options = {
{Options::BUCKET, "2"},
{Options::BUCKET_KEY, "f0"},
{"fields.f0,f1.sequence-group", "f2"},
{"fields.f0.aggregate-function", "min"},
};
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, partition_keys, primary_keys, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Should not define aggregation function on sequence group");
}
}
TEST(SchemaValidationTest, ValidateInvalidConfiguration) {
auto f0 = arrow::field("f0", arrow::utf8());
auto f1 = arrow::field("f1", arrow::int32());
auto f2 = arrow::field("f2", arrow::float64());
arrow::FieldVector fields = {f0, f1, f2};
auto schema = arrow::schema(fields);
{
std::map<std::string, std::string> options = {{Options::CHANGELOG_PRODUCER, "input"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{},
/*primary_keys=*/{}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Can not set changelog-producer on table without primary keys, please "
"define primary keys.");
}
{
auto invalid_field = arrow::field("_SEQUENCE_NUMBER", arrow::int64());
arrow::FieldVector invalid_fields = fields;
invalid_fields.push_back(invalid_field);
auto invalid_schema = arrow::schema(invalid_fields);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, invalid_schema, /*partition_keys=*/{},
/*primary_keys=*/{}, /*options=*/{}));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"field name '_SEQUENCE_NUMBER' in schema cannot be special field.");
}
{
auto invalid_field = arrow::field("_KEY_a", arrow::int64());
arrow::FieldVector invalid_fields = fields;
invalid_fields.push_back(invalid_field);
auto invalid_schema = arrow::schema(invalid_fields);
ASSERT_OK_AND_ASSIGN(
std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, invalid_schema, /*partition_keys=*/{},
/*primary_keys=*/{}, /*options=*/{}));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"field name '_KEY_a' in schema cannot start with '_KEY_'");
}
{
std::map<std::string, std::string> options = {{Options::CHANGELOG_PRODUCER, "input"},
{Options::MERGE_ENGINE, "first-row"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{},
/*primary_keys=*/{"f0"}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"C++ Paimon does not support changelog-producer yet");
}
{
std::map<std::string, std::string> options = {{Options::CHANGELOG_PRODUCER, "lookup"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{},
/*primary_keys=*/{"f0"}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"C++ Paimon does not support changelog-producer yet");
}
// test for row tracking
{
std::map<std::string, std::string> options = {{Options::ROW_TRACKING_ENABLED, "true"},
{Options::BUCKET, "1"},
{Options::BUCKET_KEY, "f0"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{},
/*primary_keys=*/{}, options));
ASSERT_NOK_WITH_MSG(
SchemaValidation::ValidateTableSchema(*table_schema),
"Cannot define bucket for row tracking table, it only support bucket = -1");
}
{
std::map<std::string, std::string> options = {{Options::ROW_TRACKING_ENABLED, "true"},
{Options::BUCKET, "-1"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{},
/*primary_keys=*/{"f0"}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Cannot define primary key for row tracking table");
}
{
std::map<std::string, std::string> options = {{Options::DATA_EVOLUTION_ENABLED, "true"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{},
/*primary_keys=*/{}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Data evolution config must enabled with row-tracking.enabled");
}
{
std::map<std::string, std::string> options = {{Options::ROW_TRACKING_ENABLED, "true"},
{Options::DATA_EVOLUTION_ENABLED, "true"},
{Options::DELETION_VECTORS_ENABLED, "true"}};
ASSERT_OK_AND_ASSIGN(std::shared_ptr<TableSchema> table_schema,
TableSchema::Create(/*schema_id=*/0, schema, /*partition_keys=*/{},
/*primary_keys=*/{}, options));
ASSERT_NOK_WITH_MSG(SchemaValidation::ValidateTableSchema(*table_schema),
"Data evolution config must disabled with deletion-vectors.enabled");
}
}
} // namespace paimon::test