| /* | |
| * 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 "iceberg/update/update_schema.h" | |
| #include <format> | |
| #include <limits> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <string_view> | |
| #include <vector> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "iceberg/catalog/memory/in_memory_catalog.h" | |
| #include "iceberg/expression/literal.h" | |
| #include "iceberg/schema.h" | |
| #include "iceberg/schema_field.h" | |
| #include "iceberg/table.h" | |
| #include "iceberg/table_identifier.h" | |
| #include "iceberg/table_metadata.h" | |
| #include "iceberg/test/matchers.h" | |
| #include "iceberg/test/mock_io.h" | |
| #include "iceberg/test/test_resource.h" | |
| #include "iceberg/type.h" | |
| #include "iceberg/util/checked_cast.h" | |
| #include "iceberg/util/uuid.h" | |
| namespace iceberg { | |
| using internal::checked_cast; | |
| class UpdateSchemaTest : public ::testing::Test { | |
| protected: | |
| virtual std::string MetadataResource() const { return "TableMetadataV2Valid.json"; } | |
| virtual std::string TableName() const { return "test_table"; } | |
| void SetUp() override { | |
| table_ident_ = TableIdentifier{.name = TableName()}; | |
| table_location_ = "/warehouse/" + TableName(); | |
| InitializeFileIO(); | |
| RegisterTableFromResource(MetadataResource()); | |
| } | |
| void InitializeFileIO() { | |
| auto mock_file_io = std::make_shared<::testing::NiceMock<MockFileIO>>(); | |
| auto* mock_file_io_ptr = mock_file_io.get(); | |
| ON_CALL(*mock_file_io, ReadFile(::testing::_, ::testing::_)) | |
| .WillByDefault([mock_file_io_ptr](const std::string& file_location, | |
| std::optional<size_t> length) { | |
| return mock_file_io_ptr->FileIO::ReadFile(file_location, length); | |
| }); | |
| ON_CALL(*mock_file_io, WriteFile(::testing::_, ::testing::_)) | |
| .WillByDefault([mock_file_io_ptr](const std::string& file_location, | |
| std::string_view content) { | |
| return mock_file_io_ptr->FileIO::WriteFile(file_location, content); | |
| }); | |
| file_io_ = std::move(mock_file_io); | |
| ICEBERG_UNWRAP_OR_FAIL(catalog_, | |
| InMemoryCatalog::Make("test_catalog", file_io_, "/warehouse/", | |
| /*properties=*/{})); | |
| } | |
| void RegisterTable(std::unique_ptr<TableMetadata> metadata) { | |
| auto metadata_location = std::format("{}/metadata/00001-{}.metadata.json", | |
| table_location_, Uuid::GenerateV7().ToString()); | |
| metadata->location = table_location_; | |
| ASSERT_THAT(TableMetadataUtil::Write(*file_io_, metadata_location, *metadata), | |
| IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(table_, | |
| catalog_->RegisterTable(table_ident_, metadata_location)); | |
| } | |
| void RegisterTableFromResource(const std::string& resource_name) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto metadata, ReadTableMetadataFromResource(resource_name)); | |
| RegisterTable(std::move(metadata)); | |
| } | |
| TableIdentifier table_ident_; | |
| std::string table_location_; | |
| std::shared_ptr<FileIO> file_io_; | |
| std::shared_ptr<InMemoryCatalog> catalog_; | |
| std::shared_ptr<Table> table_; | |
| }; | |
| TEST_F(UpdateSchemaTest, AddOptionalColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "A new integer column"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| const auto& new_field = new_field_opt->get(); | |
| EXPECT_EQ(new_field.name(), "new_col"); | |
| EXPECT_EQ(new_field.type(), int32()); | |
| EXPECT_TRUE(new_field.optional()); | |
| EXPECT_EQ(new_field.doc(), "A new integer column"); | |
| } | |
| TEST_F(UpdateSchemaTest, AddRequiredColumnFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddRequiredColumn("required_col", string(), "A required string column"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Incompatible change")); | |
| } | |
| TEST_F(UpdateSchemaTest, AddRequiredColumnWithAllowIncompatible) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges().AddRequiredColumn("required_col", string(), | |
| "A required string column"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, | |
| result.schema->FindFieldByName("required_col")); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| const auto& new_field = new_field_opt->get(); | |
| EXPECT_EQ(new_field.name(), "required_col"); | |
| EXPECT_EQ(new_field.type(), string()); | |
| EXPECT_FALSE(new_field.optional()); | |
| EXPECT_EQ(new_field.doc(), "A required string column"); | |
| } | |
| /// Uses v3 metadata for default-value tests. | |
| class UpdateSchemaDefaultValueTest : public UpdateSchemaTest { | |
| protected: | |
| void SetUp() override { | |
| table_ident_ = TableIdentifier{.name = TableName()}; | |
| table_location_ = "/warehouse/" + TableName(); | |
| InitializeFileIO(); | |
| ICEBERG_UNWRAP_OR_FAIL(auto metadata, | |
| ReadTableMetadataFromResource("TableMetadataV2Valid.json")); | |
| metadata->format_version = TableMetadata::kSupportedTableFormatVersion; | |
| metadata->next_row_id = TableMetadata::kInitialRowId; | |
| RegisterTable(std::move(metadata)); | |
| } | |
| }; | |
| TEST_F(UpdateSchemaTest, AddColumnWithDefaultValueRequiresV3) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::Int(42)); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidSchema)); | |
| EXPECT_THAT(result, HasErrorMessage("is not supported until v3")); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddColumnWithDefaultValue) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::Int(42)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| const auto& new_field = new_field_opt->get(); | |
| ASSERT_NE(new_field.initial_default(), nullptr); | |
| EXPECT_EQ(*new_field.initial_default(), Literal::Int(42)); | |
| ASSERT_NE(new_field.write_default(), nullptr); | |
| EXPECT_EQ(*new_field.write_default(), Literal::Int(42)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddRequiredColumnWithDefaultValue) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddRequiredColumn("required_col", string(), "A required string column", | |
| Literal::String("n/a")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, | |
| result.schema->FindFieldByName("required_col")); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| const auto& new_field = new_field_opt->get(); | |
| EXPECT_FALSE(new_field.optional()); | |
| ASSERT_NE(new_field.initial_default(), nullptr); | |
| EXPECT_EQ(*new_field.initial_default(), Literal::String("n/a")); | |
| ASSERT_NE(new_field.write_default(), nullptr); | |
| EXPECT_EQ(*new_field.write_default(), Literal::String("n/a")); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddColumnWithMismatchedDefaultValueFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::String("oops")); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot cast default value")); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddColumnWithNarrowingDefaultValueFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", | |
| Literal::Long(std::numeric_limits<int64_t>::max())); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot cast default value")); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnDefault) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::Int(42)) | |
| .UpdateColumnDefault("new_col", Literal::Int(7)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| const auto& new_field = new_field_opt->get(); | |
| ASSERT_NE(new_field.initial_default(), nullptr); | |
| EXPECT_EQ(*new_field.initial_default(), Literal::Int(42)); | |
| ASSERT_NE(new_field.write_default(), nullptr); | |
| EXPECT_EQ(*new_field.write_default(), Literal::Int(7)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnDefaultOnExistingColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->UpdateColumnDefault("x", Literal::Long(0)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("x")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(field.initial_default(), nullptr); | |
| ASSERT_NE(field.write_default(), nullptr); | |
| EXPECT_EQ(*field.write_default(), Literal::Long(0)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnDefaultClearsWithNullopt) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::Int(42)) | |
| .UpdateColumnDefault("new_col", std::nullopt); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| ASSERT_NE(field.initial_default(), nullptr); | |
| EXPECT_EQ(*field.initial_default(), Literal::Int(42)); | |
| EXPECT_EQ(field.write_default(), nullptr); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddNestedColumnPreservesNestedDefaults) { | |
| auto nested_type = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(/*field_id=*/100, "inner", int32(), /*optional=*/false, /*doc=*/{}, | |
| std::make_shared<const Literal>(Literal::Int(5)), | |
| std::make_shared<const Literal>(Literal::Int(9)))}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("outer", nested_type, "A nested column"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto outer_opt, result.schema->FindFieldByName("outer")); | |
| ASSERT_TRUE(outer_opt.has_value()); | |
| const auto& outer_struct = | |
| internal::checked_cast<const StructType&>(*outer_opt->get().type()); | |
| ASSERT_EQ(outer_struct.fields().size(), 1); | |
| const SchemaField& inner = outer_struct.fields()[0]; | |
| ASSERT_NE(inner.initial_default(), nullptr); | |
| EXPECT_EQ(*inner.initial_default(), Literal::Int(5)); | |
| ASSERT_NE(inner.write_default(), nullptr); | |
| EXPECT_EQ(*inner.write_default(), Literal::Int(9)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnDefaultCastsToColumnType) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->UpdateColumnDefault("x", Literal::Int(5)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("x")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| ASSERT_NE(field.write_default(), nullptr); | |
| EXPECT_EQ(*field.write_default(), Literal::Long(5)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, RequireColumnAddedWithDefault) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::Int(42)) | |
| .RequireColumn("new_col"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| EXPECT_FALSE(new_field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, RequireNestedMapListColumnAddedWithDefault) { | |
| // Map/list paths omit value/element. | |
| auto map_key_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(20, "address", string(), false)}); | |
| auto map_value_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(12, "lat", float32(), false), | |
| SchemaField(13, "long", float32(), false)}); | |
| auto map_type = | |
| std::make_shared<MapType>(SchemaField(10, "key", map_key_struct, false), | |
| SchemaField(11, "value", map_value_struct, false)); | |
| auto list_element_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(15, "x", int64(), false), SchemaField(16, "y", int64(), false)}); | |
| auto list_type = | |
| std::make_shared<ListType>(SchemaField(14, "element", list_element_struct, true)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("locations", map_type, "map of address to coordinate") | |
| .AddColumn("points", list_type, "2-D cartesian points"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("locations", "alt", float32(), "altitude", Literal::Float(0.0f)) | |
| .RequireColumn("locations.alt") | |
| .AddColumn("points", "z", int64(), "z coordinate", Literal::Long(0)) | |
| .RequireColumn("points.z"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto locations_opt, result.schema->FindFieldByName("locations")); | |
| ASSERT_TRUE(locations_opt.has_value()); | |
| const auto& map = checked_cast<const MapType&>(*locations_opt->get().type()); | |
| const auto& value_struct = checked_cast<const StructType&>(*map.value().type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto alt_opt, value_struct.GetFieldByName("alt")); | |
| ASSERT_TRUE(alt_opt.has_value()); | |
| EXPECT_FALSE(alt_opt->get().optional()); | |
| ASSERT_NE(alt_opt->get().initial_default(), nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto points_opt, result.schema->FindFieldByName("points")); | |
| ASSERT_TRUE(points_opt.has_value()); | |
| const auto& list = checked_cast<const ListType&>(*points_opt->get().type()); | |
| const auto& element_struct = checked_cast<const StructType&>(*list.element().type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto z_opt, element_struct.GetFieldByName("z")); | |
| ASSERT_TRUE(z_opt.has_value()); | |
| EXPECT_FALSE(z_opt->get().optional()); | |
| ASSERT_NE(z_opt->get().initial_default(), nullptr); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnDocPreservesDefaultValues) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::Int(42)) | |
| .UpdateColumnDoc("new_col", "updated doc"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(field.doc(), "updated doc"); | |
| ASSERT_NE(field.initial_default(), nullptr); | |
| EXPECT_EQ(*field.initial_default(), Literal::Int(42)); | |
| ASSERT_NE(field.write_default(), nullptr); | |
| EXPECT_EQ(*field.write_default(), Literal::Int(42)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnTypePromotesDefaultValues) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32(), "An integer column", Literal::Int(42)) | |
| .UpdateColumn("new_col", int64()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(field.type(), int64()); | |
| ASSERT_NE(field.initial_default(), nullptr); | |
| EXPECT_EQ(*field.initial_default(), Literal::Long(42)); | |
| ASSERT_NE(field.write_default(), nullptr); | |
| EXPECT_EQ(*field.write_default(), Literal::Long(42)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnTypePromotesDecimalDefault) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update | |
| ->AddColumn("new_col", decimal(9, 2), "A decimal column", | |
| Literal::Decimal(1234, 9, 2)) | |
| .UpdateColumn("new_col", decimal(18, 2)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(field.type()->ToString(), decimal(18, 2)->ToString()); | |
| ASSERT_NE(field.initial_default(), nullptr); | |
| EXPECT_EQ(*field.initial_default(), Literal::Decimal(1234, 18, 2)); | |
| ASSERT_NE(field.write_default(), nullptr); | |
| EXPECT_EQ(*field.write_default(), Literal::Decimal(1234, 18, 2)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddColumnWithWiderPrecisionDecimalDefault) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", decimal(18, 2), "A decimal column", | |
| Literal::Decimal(1234, 9, 2)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| ASSERT_NE(field.initial_default(), nullptr); | |
| EXPECT_EQ(*field.initial_default(), Literal::Decimal(1234, 18, 2)); | |
| ASSERT_NE(field.write_default(), nullptr); | |
| EXPECT_EQ(*field.write_default(), Literal::Decimal(1234, 18, 2)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnDefaultWiderPrecisionDecimal) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", decimal(18, 2), "A decimal column") | |
| .UpdateColumnDefault("new_col", Literal::Decimal(1234, 9, 2)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("new_col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| ASSERT_NE(field.write_default(), nullptr); | |
| EXPECT_EQ(*field.write_default(), Literal::Decimal(1234, 18, 2)); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddColumnWithDifferentScaleDecimalDefaultFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", decimal(18, 2), "A decimal column", | |
| Literal::Decimal(1234, 9, 3)); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot cast default value")); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, UpdateColumnDefaultDifferentScaleDecimalFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", decimal(18, 2), "A decimal column") | |
| .UpdateColumnDefault("new_col", Literal::Decimal(1234, 9, 3)); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot cast default value")); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddColumnWithOutOfPrecisionDecimalDefaultFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", decimal(4, 2), "A decimal column", | |
| Literal::Decimal(1234567, 9, 2)); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot cast default value")); | |
| } | |
| TEST_F(UpdateSchemaDefaultValueTest, AddColumnWithTypedNullDecimalDefaultFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", decimal(18, 2), "A decimal column", | |
| Literal::Null(decimal(18, 2))); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot cast default value")); | |
| } | |
| TEST_F(UpdateSchemaTest, AddMultipleColumns) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("col1", int32(), "First column") | |
| .AddColumn("col2", string(), "Second column") | |
| .AddColumn("col3", boolean(), "Third column"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto col1_opt, result.schema->FindFieldByName("col1")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto col2_opt, result.schema->FindFieldByName("col2")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto col3_opt, result.schema->FindFieldByName("col3")); | |
| ASSERT_TRUE(col1_opt.has_value()); | |
| ASSERT_TRUE(col2_opt.has_value()); | |
| ASSERT_TRUE(col3_opt.has_value()); | |
| EXPECT_EQ(col1_opt->get().type(), int32()); | |
| EXPECT_EQ(col2_opt->get().type(), string()); | |
| EXPECT_EQ(col3_opt->get().type(), boolean()); | |
| } | |
| TEST_F(UpdateSchemaTest, AddColumnWithDotInNameFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("col.with.dots", int32(), "Column with dots"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot add column with ambiguous name")); | |
| } | |
| TEST_F(UpdateSchemaTest, AddColumnToNestedStruct) { | |
| auto struct_type = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(100, "nested_field", int32(), true, "Nested field")}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("struct_col", struct_type, "A struct column"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("struct_col", "new_nested_field", string(), "New nested field"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_field_opt, | |
| result.schema->FindFieldByName("struct_col")); | |
| ASSERT_TRUE(struct_field_opt.has_value()); | |
| const auto& struct_field = struct_field_opt->get(); | |
| ASSERT_TRUE(struct_field.type()->is_nested()); | |
| const auto& nested_struct = checked_cast<const StructType&>(*struct_field.type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto nested_field_opt, | |
| nested_struct.GetFieldByName("new_nested_field")); | |
| ASSERT_TRUE(nested_field_opt.has_value()); | |
| const auto& nested_field = nested_field_opt->get(); | |
| EXPECT_EQ(nested_field.name(), "new_nested_field"); | |
| EXPECT_EQ(nested_field.type(), string()); | |
| } | |
| TEST_F(UpdateSchemaTest, AddColumnToNonExistentParentFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("non_existent_parent", "new_field", int32(), "New field"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot find parent struct")); | |
| } | |
| TEST_F(UpdateSchemaTest, AddColumnToNonStructParentFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("primitive_col", int32(), "A primitive column"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("primitive_col", "nested_field", string(), "Should fail"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot add to non-struct column")); | |
| } | |
| TEST_F(UpdateSchemaTest, AddDuplicateColumnNameFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("duplicate_col", int32(), "First column") | |
| .AddColumn("duplicate_col", string(), "Duplicate column"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidSchema)); | |
| EXPECT_THAT(result, HasErrorMessage("Duplicate path found")); | |
| } | |
| TEST_F(UpdateSchemaTest, ColumnIdAssignment) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto original_schema, table_->schema()); | |
| int32_t original_last_id = table_->metadata()->last_column_id; | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col1", int32(), "First new column") | |
| .AddColumn("new_col2", string(), "Second new column"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| EXPECT_EQ(result.new_last_column_id, original_last_id + 2); | |
| ICEBERG_UNWRAP_OR_FAIL(auto col1_opt, result.schema->FindFieldByName("new_col1")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto col2_opt, result.schema->FindFieldByName("new_col2")); | |
| ASSERT_TRUE(col1_opt.has_value()); | |
| ASSERT_TRUE(col2_opt.has_value()); | |
| EXPECT_EQ(col1_opt->get().field_id(), original_last_id + 1); | |
| EXPECT_EQ(col2_opt->get().field_id(), original_last_id + 2); | |
| } | |
| TEST_F(UpdateSchemaTest, AddNestedStructColumn) { | |
| auto nested_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(100, "field1", int32(), true, "First nested field"), | |
| SchemaField(101, "field2", string(), false, "Second nested field")}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("complex_struct", nested_struct, "A complex struct column"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_field_opt, | |
| result.schema->FindFieldByName("complex_struct")); | |
| ASSERT_TRUE(struct_field_opt.has_value()); | |
| const auto& struct_field = struct_field_opt->get(); | |
| EXPECT_EQ(struct_field.name(), "complex_struct"); | |
| EXPECT_TRUE(struct_field.type()->is_nested()); | |
| EXPECT_TRUE(struct_field.optional()); | |
| const auto& nested_type = checked_cast<const StructType&>(*struct_field.type()); | |
| EXPECT_EQ(nested_type.fields().size(), 2); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field1_opt, nested_type.GetFieldByName("field1")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field2_opt, nested_type.GetFieldByName("field2")); | |
| ASSERT_TRUE(field1_opt.has_value()); | |
| ASSERT_TRUE(field2_opt.has_value()); | |
| EXPECT_EQ(field1_opt->get().type(), int32()); | |
| EXPECT_EQ(field2_opt->get().type(), string()); | |
| EXPECT_TRUE(field1_opt->get().optional()); | |
| EXPECT_FALSE(field2_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, CaseSensitiveColumnNames) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(true) | |
| .AddColumn("Column", int32(), "Uppercase column") | |
| .AddColumn("column", string(), "Lowercase column"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto upper_opt, result.schema->FindFieldByName("Column", true)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto lower_opt, result.schema->FindFieldByName("column", true)); | |
| ASSERT_TRUE(upper_opt.has_value()); | |
| ASSERT_TRUE(lower_opt.has_value()); | |
| EXPECT_EQ(upper_opt->get().type(), int32()); | |
| EXPECT_EQ(lower_opt->get().type(), string()); | |
| } | |
| TEST_F(UpdateSchemaTest, CaseInsensitiveDuplicateDetection) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(false) | |
| .AddColumn("Column", int32(), "First column") | |
| .AddColumn("COLUMN", string(), "Duplicate column"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidSchema)); | |
| EXPECT_THAT(result, HasErrorMessage("Duplicate path found")); | |
| } | |
| TEST_F(UpdateSchemaTest, EmptyUpdate) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto original_schema, table_->schema()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| EXPECT_EQ(*result.schema, *original_schema); | |
| EXPECT_EQ(result.new_last_column_id, table_->metadata()->last_column_id); | |
| } | |
| TEST_F(UpdateSchemaTest, CommitSuccess) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("committed_col", int64(), "A committed column"); | |
| EXPECT_THAT(update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto schema, reloaded->schema()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, schema->FindFieldByName("committed_col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(field.name(), "committed_col"); | |
| EXPECT_EQ(*field.type(), *int64()); | |
| EXPECT_TRUE(field.optional()); | |
| EXPECT_EQ(field.doc(), "A committed column"); | |
| } | |
| TEST_F(UpdateSchemaTest, AddFieldsToMapAndList) { | |
| auto map_key_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(20, "address", string(), false), | |
| SchemaField(21, "city", string(), false)}); | |
| auto map_value_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(12, "lat", float32(), false), | |
| SchemaField(13, "long", float32(), false)}); | |
| auto map_type = | |
| std::make_shared<MapType>(SchemaField(10, "key", map_key_struct, false), | |
| SchemaField(11, "value", map_value_struct, false)); | |
| auto list_element_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(15, "x", int64(), false), SchemaField(16, "y", int64(), false)}); | |
| auto list_type = | |
| std::make_shared<ListType>(SchemaField(14, "element", list_element_struct, true)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("locations", map_type, "map of address to coordinate") | |
| .AddColumn("points", list_type, "2-D cartesian points"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("locations", "alt", float32(), "altitude") | |
| .AddColumn("points", "z", int64(), "z coordinate"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto locations_opt, result.schema->FindFieldByName("locations")); | |
| ASSERT_TRUE(locations_opt.has_value()); | |
| const auto& locations_field = locations_opt->get(); | |
| ASSERT_EQ(locations_field.type()->type_id(), TypeId::kMap); | |
| const auto& map = checked_cast<const MapType&>(*locations_field.type()); | |
| const auto& value_struct = checked_cast<const StructType&>(*map.value().type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto alt_opt, value_struct.GetFieldByName("alt")); | |
| ASSERT_TRUE(alt_opt.has_value()); | |
| EXPECT_EQ(alt_opt->get().type(), float32()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto points_opt, result.schema->FindFieldByName("points")); | |
| ASSERT_TRUE(points_opt.has_value()); | |
| const auto& points_field = points_opt->get(); | |
| ASSERT_EQ(points_field.type()->type_id(), TypeId::kList); | |
| const auto& list = checked_cast<const ListType&>(*points_field.type()); | |
| const auto& element_struct = checked_cast<const StructType&>(*list.element().type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto z_opt, element_struct.GetFieldByName("z")); | |
| ASSERT_TRUE(z_opt.has_value()); | |
| EXPECT_EQ(z_opt->get().type(), int64()); | |
| } | |
| TEST_F(UpdateSchemaTest, AddNestedStructWithIdReassignment) { | |
| auto nested_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(1, "lat", int32(), false), SchemaField(2, "long", int32(), true)}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("location", nested_struct); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto location_opt, result.schema->FindFieldByName("location")); | |
| ASSERT_TRUE(location_opt.has_value()); | |
| const auto& location_field = location_opt->get(); | |
| ASSERT_TRUE(location_field.type()->is_nested()); | |
| const auto& struct_type = checked_cast<const StructType&>(*location_field.type()); | |
| ASSERT_EQ(struct_type.fields().size(), 2); | |
| ICEBERG_UNWRAP_OR_FAIL(auto lat_opt, struct_type.GetFieldByName("lat")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto long_opt, struct_type.GetFieldByName("long")); | |
| ASSERT_TRUE(lat_opt.has_value()); | |
| ASSERT_TRUE(long_opt.has_value()); | |
| EXPECT_GT(lat_opt->get().field_id(), 1); | |
| EXPECT_GT(long_opt->get().field_id(), 1); | |
| } | |
| TEST_F(UpdateSchemaTest, AddNestedMapOfStructs) { | |
| auto key_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(20, "address", string(), false), | |
| SchemaField(21, "city", string(), false), SchemaField(22, "state", string(), false), | |
| SchemaField(23, "zip", int32(), false)}); | |
| auto value_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(9, "lat", int32(), false), SchemaField(8, "long", int32(), true)}); | |
| auto map_type = std::make_shared<MapType>(SchemaField(1, "key", key_struct, false), | |
| SchemaField(2, "value", value_struct, true)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("locations", map_type); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto locations_opt, result.schema->FindFieldByName("locations")); | |
| ASSERT_TRUE(locations_opt.has_value()); | |
| const auto& locations_field = locations_opt->get(); | |
| ASSERT_EQ(locations_field.type()->type_id(), TypeId::kMap); | |
| const auto& map = checked_cast<const MapType&>(*locations_field.type()); | |
| const auto& key_struct_type = checked_cast<const StructType&>(*map.key().type()); | |
| EXPECT_EQ(key_struct_type.fields().size(), 4); | |
| const auto& value_struct_type = checked_cast<const StructType&>(*map.value().type()); | |
| EXPECT_EQ(value_struct_type.fields().size(), 2); | |
| ICEBERG_UNWRAP_OR_FAIL(auto lat_opt, value_struct_type.GetFieldByName("lat")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto long_opt, value_struct_type.GetFieldByName("long")); | |
| ASSERT_TRUE(lat_opt.has_value()); | |
| ASSERT_TRUE(long_opt.has_value()); | |
| } | |
| TEST_F(UpdateSchemaTest, AddNestedListOfStructs) { | |
| auto element_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(9, "lat", int32(), false), SchemaField(8, "long", int32(), true)}); | |
| auto list_type = | |
| std::make_shared<ListType>(SchemaField(1, "element", element_struct, true)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("locations", list_type); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto locations_opt, result.schema->FindFieldByName("locations")); | |
| ASSERT_TRUE(locations_opt.has_value()); | |
| const auto& locations_field = locations_opt->get(); | |
| ASSERT_EQ(locations_field.type()->type_id(), TypeId::kList); | |
| const auto& list = checked_cast<const ListType&>(*locations_field.type()); | |
| const auto& element_struct_type = | |
| checked_cast<const StructType&>(*list.element().type()); | |
| EXPECT_EQ(element_struct_type.fields().size(), 2); | |
| ICEBERG_UNWRAP_OR_FAIL(auto lat_opt, element_struct_type.GetFieldByName("lat")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto long_opt, element_struct_type.GetFieldByName("long")); | |
| ASSERT_TRUE(lat_opt.has_value()); | |
| ASSERT_TRUE(long_opt.has_value()); | |
| } | |
| TEST_F(UpdateSchemaTest, AddFieldWithDotsInName) { | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "field1", int32(), true)}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("struct_col", struct_type); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("struct_col", "field.with.dots", int64(), "Field with dots in name"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_field_opt, | |
| result.schema->FindFieldByName("struct_col")); | |
| ASSERT_TRUE(struct_field_opt.has_value()); | |
| const auto& struct_field = struct_field_opt->get(); | |
| const auto& nested_struct = checked_cast<const StructType&>(*struct_field.type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto dotted_field_opt, | |
| nested_struct.GetFieldByName("field.with.dots")); | |
| ASSERT_TRUE(dotted_field_opt.has_value()); | |
| EXPECT_EQ(dotted_field_opt->get().name(), "field.with.dots"); | |
| EXPECT_EQ(dotted_field_opt->get().type(), int64()); | |
| } | |
| TEST_F(UpdateSchemaTest, AddFieldToMapKeyFails) { | |
| auto key_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(20, "address", string(), false)}); | |
| auto value_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(12, "lat", float32(), false)}); | |
| auto map_type = | |
| std::make_shared<MapType>(SchemaField(10, "key", key_struct, false), | |
| SchemaField(11, "value", value_struct, false)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("locations", map_type); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("locations.key", "city", string(), "Should fail"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot add fields to map keys")); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("to_delete", string(), "A column to delete"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("to_delete"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("to_delete")); | |
| EXPECT_FALSE(field_opt.has_value()); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteNestedColumn) { | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "field1", int32(), true), | |
| SchemaField(101, "field2", string(), true)}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("struct_col", struct_type); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("struct_col.field1"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_field_opt, | |
| result.schema->FindFieldByName("struct_col")); | |
| ASSERT_TRUE(struct_field_opt.has_value()); | |
| const auto& struct_field = struct_field_opt->get(); | |
| const auto& nested_struct = checked_cast<const StructType&>(*struct_field.type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field1_opt, nested_struct.GetFieldByName("field1")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field2_opt, nested_struct.GetFieldByName("field2")); | |
| EXPECT_FALSE(field1_opt.has_value()); | |
| EXPECT_TRUE(field2_opt.has_value()); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteMissingColumnFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->DeleteColumn("non_existent"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot delete missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteThenAdd) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AllowIncompatibleChanges().AddRequiredColumn("col", int32(), | |
| "Required column"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col").AddColumn("col", string(), "Now optional string"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(field.type(), string()); | |
| EXPECT_TRUE(field.optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteThenAddNested) { | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "field1", boolean(), false)}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("struct_col", struct_type); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("struct_col.field1") | |
| .AddColumn("struct_col", "field1", int32(), "Re-added field"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_field_opt, | |
| result.schema->FindFieldByName("struct_col")); | |
| ASSERT_TRUE(struct_field_opt.has_value()); | |
| const auto& struct_field = struct_field_opt->get(); | |
| const auto& nested_struct = checked_cast<const StructType&>(*struct_field.type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field1_opt, nested_struct.GetFieldByName("field1")); | |
| ASSERT_TRUE(field1_opt.has_value()); | |
| EXPECT_EQ(field1_opt->get().type(), int32()); | |
| } | |
| TEST_F(UpdateSchemaTest, AddDeleteConflict) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("new_col", int32()).DeleteColumn("new_col"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot delete missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteColumnWithAdditionsFails) { | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "field1", int32(), true)}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("struct_col", struct_type); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("struct_col", "field2", string()).DeleteColumn("struct_col"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot delete a column that has additions")); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteMapKeyFails) { | |
| auto map_type = std::make_shared<MapType>(SchemaField(10, "key", string(), false), | |
| SchemaField(11, "value", int32(), true)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("map_col", map_type); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("map_col.key"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot delete map keys")); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteColumnCaseInsensitive) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("MyColumn", string(), "A column with mixed case"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->CaseSensitive(false).DeleteColumn("mycolumn"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, | |
| result.schema->FindFieldByName("MyColumn", false)); | |
| EXPECT_FALSE(field_opt.has_value()); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("old_name", string(), "A column to rename"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RenameColumn("old_name", "new_name"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto old_field_opt, result.schema->FindFieldByName("old_name")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, result.schema->FindFieldByName("new_name")); | |
| EXPECT_FALSE(old_field_opt.has_value()); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| EXPECT_EQ(*new_field_opt->get().type(), *string()); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameNestedColumn) { | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "field1", int32(), true), | |
| SchemaField(101, "field2", string(), true)}); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("struct_col", struct_type); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RenameColumn("struct_col.field1", "renamed_field"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto struct_field_opt, | |
| result.schema->FindFieldByName("struct_col")); | |
| ASSERT_TRUE(struct_field_opt.has_value()); | |
| const auto& struct_field = struct_field_opt->get(); | |
| const auto& nested_struct = checked_cast<const StructType&>(*struct_field.type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field1_opt, nested_struct.GetFieldByName("field1")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto renamed_opt, nested_struct.GetFieldByName("renamed_field")); | |
| EXPECT_FALSE(field1_opt.has_value()); | |
| ASSERT_TRUE(renamed_opt.has_value()); | |
| EXPECT_EQ(*renamed_opt->get().type(), *int32()); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameColumnWithDotsInName) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("simple_name", string()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RenameColumn("simple_name", "name.with.dots"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, | |
| result.schema->FindFieldByName("name.with.dots")); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| EXPECT_EQ(*new_field_opt->get().type(), *string()); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameMissingColumnFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->RenameColumn("non_existent", "new_name"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot rename missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameDeletedColumnFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", string()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col").RenameColumn("col", "new_name"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot rename a column that will be deleted")); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameColumnCaseInsensitive) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("MyColumn", string(), "A column with mixed case"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->CaseSensitive(false).RenameColumn("mycolumn", "NewName"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto old_field_opt, | |
| result.schema->FindFieldByName("MyColumn", false)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto new_field_opt, | |
| result.schema->FindFieldByName("NewName", false)); | |
| EXPECT_FALSE(old_field_opt.has_value()); | |
| ASSERT_TRUE(new_field_opt.has_value()); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameThenDeleteOldNameFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("old_name", string()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RenameColumn("old_name", "new_name").DeleteColumn("old_name"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot delete a column that has updates")); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameThenDeleteNewNameFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("old_name", string()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RenameColumn("old_name", "new_name").DeleteColumn("new_name"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot delete missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameThenAddWithOldName) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("old_name", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RenameColumn("old_name", "new_name").AddColumn("old_name", string()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot add column, name already exists")); | |
| } | |
| TEST_F(UpdateSchemaTest, AddThenRename) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("temp_name", string()).RenameColumn("temp_name", "final_name"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot rename missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteThenAddThenRename) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col") | |
| .AddColumn("col", string(), "New column with same name") | |
| .RenameColumn("col", "renamed_col"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot rename a column that will be deleted")); | |
| } | |
| TEST_F(UpdateSchemaTest, MakeColumnOptional) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AllowIncompatibleChanges().AddRequiredColumn("id", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MakeColumnOptional("id"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_TRUE(field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, RequireColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("id", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RequireColumn("id"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot change column nullability")); | |
| EXPECT_THAT(result, HasErrorMessage("optional -> required")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded2, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update2, reloaded2->NewUpdateSchema()); | |
| update2->AllowIncompatibleChanges().RequireColumn("id"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result2, update2->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result2.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_FALSE(field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, RequireColumnNoop) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AllowIncompatibleChanges().AddRequiredColumn("id", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RequireColumn("id"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_FALSE(field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, MakeColumnOptionalNoop) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("id", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MakeColumnOptional("id"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_TRUE(field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, RequireColumnCaseInsensitive) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("ID", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->CaseSensitive(false).AllowIncompatibleChanges().RequireColumn("id"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("ID", false)); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_FALSE(field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, MakeColumnOptionalMissingFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->MakeColumnOptional("non_existent"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, RequireColumnMissingFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges().RequireColumn("non_existent"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, MakeColumnOptionalDeletedFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AllowIncompatibleChanges().AddRequiredColumn("col", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col").MakeColumnOptional("col"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update a column that will be deleted")); | |
| } | |
| TEST_F(UpdateSchemaTest, RequireColumnDeletedFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col").AllowIncompatibleChanges().RequireColumn("col"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update a column that will be deleted")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDoc) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("col", int32(), "original doc"); | |
| update->UpdateColumnDoc("col", "updated doc"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(field_opt->get().doc(), "updated doc"); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDocMissingFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->UpdateColumnDoc("non_existent", "some doc"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDocDeletedFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", int32(), "original doc"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col").UpdateColumnDoc("col", "new doc"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update a column that will be deleted")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDocNoop) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", int32(), "same doc"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->UpdateColumnDoc("col", "same doc"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(field_opt->get().doc(), "same doc"); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDocEmptyString) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("col", int32(), "original doc"); | |
| update->UpdateColumnDoc("col", ""); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("col")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(field_opt->get().doc(), ""); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnIntToLong) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("id", int32(), "An integer ID"); | |
| update->UpdateColumn("id", int64()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(*field_opt->get().type(), *int64()); | |
| EXPECT_EQ(field_opt->get().doc(), "An integer ID"); // Doc preserved | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnFloatToDouble) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("value", float32(), "A float value"); | |
| update->UpdateColumn("value", float64()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("value")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(*field_opt->get().type(), *float64()); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnUnknownToPrimitive) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("mystery", unknown(), "A null-only placeholder"); | |
| update->UpdateColumn("mystery", string()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("mystery")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(*field_opt->get().type(), *string()); | |
| EXPECT_TRUE(field_opt->get().optional()); | |
| EXPECT_EQ(field_opt->get().doc(), "A null-only placeholder"); | |
| } | |
| TEST_F(UpdateSchemaTest, AddRequiredUnknownColumnFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges().AddRequiredColumn("mystery", unknown()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidArgument)); | |
| EXPECT_THAT(result, HasErrorMessage("Unknown type field 'mystery' must be optional")); | |
| } | |
| TEST_F(UpdateSchemaTest, AddColumnWithRequiredNestedUnknownFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("profile", struct_({ | |
| SchemaField::MakeRequired(3, "mystery", unknown()), | |
| })); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kInvalidArgument)); | |
| EXPECT_THAT(result, HasErrorMessage("Unknown type field 'mystery' must be optional")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnSameType) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("id", int32()); | |
| update->UpdateColumn("id", int32()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(*field_opt->get().type(), *int32()); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnMissingFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->UpdateColumn("non_existent", int64()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDeletedFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col").UpdateColumn("col", int64()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot update a column that will be deleted")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnInvalidPromotionFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("id", int64()); | |
| update->UpdateColumn("id", int32()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot change column type")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnInvalidPromotionDoubleToFloatFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("value", float64()); | |
| update->UpdateColumn("value", float32()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot change column type")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnIncompatibleTypesFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("id", int32()); | |
| update->UpdateColumn("id", string()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot change column type")); | |
| } | |
| TEST_F(UpdateSchemaTest, RenameAndUpdateColumnInSameTransaction) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("old_name", int32()); | |
| update->UpdateColumn("old_name", int64()); | |
| update->RenameColumn("old_name", "new_name"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot rename missing column")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDecimalPrecisionWidening) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| auto decimal_10_2 = decimal(10, 2); | |
| auto decimal_20_2 = decimal(20, 2); | |
| update->AddColumn("price", decimal_10_2); | |
| update->UpdateColumn("price", decimal_20_2); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("price")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(*field_opt->get().type(), *decimal_20_2); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDecimalDifferentScaleFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| auto decimal_10_2 = decimal(10, 2); | |
| auto decimal_10_3 = decimal(10, 3); | |
| update->AddColumn("price", decimal_10_2); | |
| update->UpdateColumn("price", decimal_10_3); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot change column type")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateColumnDecimalPrecisionNarrowingFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| auto decimal_20_2 = decimal(20, 2); | |
| auto decimal_10_2 = decimal(10, 2); | |
| update->AddColumn("price", decimal_20_2); | |
| update->UpdateColumn("price", decimal_10_2); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot change column type")); | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateTypePreservesOtherMetadata) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("value", int32(), "A counter value"); | |
| update->UpdateColumn("value", int64()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("value")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(*field.type(), *int64()); // Type updated | |
| EXPECT_EQ(field.doc(), "A counter value"); // Doc preserved | |
| EXPECT_TRUE(field.optional()); // Optional preserved | |
| } | |
| TEST_F(UpdateSchemaTest, UpdateDocPreservesOtherMetadata) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges().AddRequiredColumn("id", int64(), "old doc"); | |
| update->UpdateColumnDoc("id", "new doc"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| const auto& field = field_opt->get(); | |
| EXPECT_EQ(field.doc(), "new doc"); // Doc updated | |
| EXPECT_EQ(*field.type(), *int64()); // Type preserved | |
| EXPECT_FALSE(field.optional()); // Optional preserved (still required) | |
| } | |
| TEST_F(UpdateSchemaTest, RenameDeleteConflict) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->RenameColumn("col", "new_name").DeleteColumn("col"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot delete a column that has updates")); | |
| } | |
| TEST_F(UpdateSchemaTest, DeleteRenameConflict) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AddColumn("col", int32()); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->DeleteColumn("col").RenameColumn("col", "new_name"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot rename a column that will be deleted")); | |
| } | |
| TEST_F(UpdateSchemaTest, CaseInsensitiveAddThenUpdate) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(false) | |
| .AddColumn("Foo", int32(), "A column with uppercase name") | |
| .UpdateColumn("foo", int64()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("Foo", false)); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(*field_opt->get().type(), *int64()); | |
| } | |
| TEST_F(UpdateSchemaTest, CaseInsensitiveAddThenUpdateDoc) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(false) | |
| .AddColumn("Foo", int32(), "original doc") | |
| .UpdateColumnDoc("foo", "updated doc"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("Foo", false)); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_EQ(field_opt->get().doc(), "updated doc"); | |
| } | |
| TEST_F(UpdateSchemaTest, CaseInsensitiveAddThenMakeOptional) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(false) | |
| .AllowIncompatibleChanges() | |
| .AddRequiredColumn("Foo", int32(), "required column") | |
| .MakeColumnOptional("foo"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("Foo", false)); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_TRUE(field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, CaseInsensitiveAddThenRequire) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(false) | |
| .AllowIncompatibleChanges() | |
| .AddColumn("Foo", int32(), "optional column") | |
| .RequireColumn("foo"); // Require using lowercase | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto field_opt, result.schema->FindFieldByName("Foo", false)); | |
| ASSERT_TRUE(field_opt.has_value()); | |
| EXPECT_FALSE(field_opt->get().optional()); | |
| } | |
| TEST_F(UpdateSchemaTest, MixedChanges) { | |
| auto preferences_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(8, "feature1", boolean(), false), | |
| SchemaField(9, "feature2", boolean(), true)}); | |
| auto address_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(20, "address", string(), false), | |
| SchemaField(21, "city", string(), false), SchemaField(22, "state", string(), false), | |
| SchemaField(23, "zip", int32(), false)}); | |
| auto coordinate_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(12, "lat", float32(), false), | |
| SchemaField(13, "long", float32(), false)}); | |
| auto locations_map = | |
| std::make_shared<MapType>(SchemaField(10, "key", address_struct, false), | |
| SchemaField(11, "value", coordinate_struct, false)); | |
| auto point_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(15, "x", int64(), false), SchemaField(16, "y", int64(), false)}); | |
| auto points_list = | |
| std::make_shared<ListType>(SchemaField(14, "element", point_struct, true)); | |
| auto doubles_list = | |
| std::make_shared<ListType>(SchemaField(17, "element", float64(), false)); | |
| auto properties_map = std::make_shared<MapType>( | |
| SchemaField(18, "key", string(), true), SchemaField(19, "value", string(), true)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup_update, table_->NewUpdateSchema()); | |
| setup_update->AllowIncompatibleChanges() | |
| .AddRequiredColumn("id", int32()) | |
| .AddRequiredColumn("data", string()) | |
| .AddColumn("preferences", preferences_struct, "struct of named boolean options") | |
| .AddRequiredColumn("locations", locations_map, "map of address to coordinate") | |
| .AddColumn("points", points_list, "2-D cartesian points") | |
| .AddRequiredColumn("doubles", doubles_list) | |
| .AddColumn("properties", properties_map, "string map of properties"); | |
| EXPECT_THAT(setup_update->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("toplevel", decimal(9, 2)) | |
| .AddColumn("locations", "alt", float32()) // add to map value struct | |
| .AddColumn("points", "z", int64()) // add to list element struct | |
| .AddColumn("points", "t.t", int64(), "name with '.'") | |
| .RenameColumn("data", "json") | |
| .RenameColumn("preferences", "options") | |
| .RenameColumn("preferences.feature2", "newfeature") // rename inside renamed column | |
| .RenameColumn("locations.lat", "latitude") | |
| .RenameColumn("points.x", "X") | |
| .RenameColumn("points.y", "y.y") // field name with dots | |
| .UpdateColumn("id", int64()) | |
| .UpdateColumnDoc("id", "unique id") | |
| .UpdateColumn("locations.lat", float64()) // use original name before rename | |
| .UpdateColumnDoc("locations.lat", "latitude") | |
| .DeleteColumn("locations.long") | |
| .DeleteColumn("properties") | |
| .MakeColumnOptional("points.x") | |
| .AllowIncompatibleChanges() | |
| .RequireColumn("data") | |
| .AddRequiredColumn("locations", "description", string(), "Location description"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto id_opt, result.schema->FindFieldByName("id")); | |
| ASSERT_TRUE(id_opt.has_value()); | |
| EXPECT_EQ(*id_opt->get().type(), *int64()); | |
| EXPECT_EQ(id_opt->get().doc(), "unique id"); | |
| EXPECT_FALSE(id_opt->get().optional()); // was required | |
| ICEBERG_UNWRAP_OR_FAIL(auto data_opt, result.schema->FindFieldByName("data")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto json_opt, result.schema->FindFieldByName("json")); | |
| EXPECT_FALSE(data_opt.has_value()); | |
| ASSERT_TRUE(json_opt.has_value()); | |
| EXPECT_EQ(*json_opt->get().type(), *string()); | |
| EXPECT_FALSE(json_opt->get().optional()); // now required | |
| ICEBERG_UNWRAP_OR_FAIL(auto preferences_opt, | |
| result.schema->FindFieldByName("preferences")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto options_opt, result.schema->FindFieldByName("options")); | |
| EXPECT_FALSE(preferences_opt.has_value()); | |
| ASSERT_TRUE(options_opt.has_value()); | |
| EXPECT_EQ(options_opt->get().doc(), "struct of named boolean options"); | |
| const auto& options_struct = | |
| checked_cast<const StructType&>(*options_opt->get().type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto feature1_opt, options_struct.GetFieldByName("feature1")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto feature2_opt, options_struct.GetFieldByName("feature2")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto newfeature_opt, | |
| options_struct.GetFieldByName("newfeature")); | |
| EXPECT_TRUE(feature1_opt.has_value()); | |
| EXPECT_FALSE(feature2_opt.has_value()); | |
| ASSERT_TRUE(newfeature_opt.has_value()); | |
| EXPECT_EQ(*newfeature_opt->get().type(), *boolean()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto locations_opt, result.schema->FindFieldByName("locations")); | |
| ASSERT_TRUE(locations_opt.has_value()); | |
| EXPECT_EQ(locations_opt->get().doc(), "map of address to coordinate"); | |
| EXPECT_FALSE(locations_opt->get().optional()); // was required | |
| const auto& locations_map_type = | |
| checked_cast<const MapType&>(*locations_opt->get().type()); | |
| const auto& coord_struct = | |
| checked_cast<const StructType&>(*locations_map_type.value().type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto lat_opt, coord_struct.GetFieldByName("lat")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto latitude_opt, coord_struct.GetFieldByName("latitude")); | |
| EXPECT_FALSE(lat_opt.has_value()); | |
| ASSERT_TRUE(latitude_opt.has_value()); | |
| EXPECT_EQ(*latitude_opt->get().type(), *float64()); | |
| EXPECT_EQ(latitude_opt->get().doc(), "latitude"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto long_opt, coord_struct.GetFieldByName("long")); | |
| EXPECT_FALSE(long_opt.has_value()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto alt_opt, coord_struct.GetFieldByName("alt")); | |
| ASSERT_TRUE(alt_opt.has_value()); | |
| EXPECT_EQ(*alt_opt->get().type(), *float32()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto description_opt, | |
| coord_struct.GetFieldByName("description")); | |
| ASSERT_TRUE(description_opt.has_value()); | |
| EXPECT_EQ(*description_opt->get().type(), *string()); | |
| EXPECT_EQ(description_opt->get().doc(), "Location description"); | |
| EXPECT_FALSE(description_opt->get().optional()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto points_opt, result.schema->FindFieldByName("points")); | |
| ASSERT_TRUE(points_opt.has_value()); | |
| EXPECT_EQ(points_opt->get().doc(), "2-D cartesian points"); | |
| const auto& points_list_type = checked_cast<const ListType&>(*points_opt->get().type()); | |
| const auto& point_elem_struct = | |
| checked_cast<const StructType&>(*points_list_type.element().type()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto x_opt, point_elem_struct.GetFieldByName("x")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto X_opt, point_elem_struct.GetFieldByName("X")); | |
| EXPECT_FALSE(x_opt.has_value()); | |
| ASSERT_TRUE(X_opt.has_value()); | |
| EXPECT_EQ(*X_opt->get().type(), *int64()); | |
| EXPECT_TRUE(X_opt->get().optional()); // made optional | |
| ICEBERG_UNWRAP_OR_FAIL(auto y_opt, point_elem_struct.GetFieldByName("y")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto yy_opt, point_elem_struct.GetFieldByName("y.y")); | |
| EXPECT_FALSE(y_opt.has_value()); | |
| ASSERT_TRUE(yy_opt.has_value()); | |
| EXPECT_EQ(*yy_opt->get().type(), *int64()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto z_opt, point_elem_struct.GetFieldByName("z")); | |
| ASSERT_TRUE(z_opt.has_value()); | |
| EXPECT_EQ(*z_opt->get().type(), *int64()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto tt_opt, point_elem_struct.GetFieldByName("t.t")); | |
| ASSERT_TRUE(tt_opt.has_value()); | |
| EXPECT_EQ(*tt_opt->get().type(), *int64()); | |
| EXPECT_EQ(tt_opt->get().doc(), "name with '.'"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto doubles_opt, result.schema->FindFieldByName("doubles")); | |
| ASSERT_TRUE(doubles_opt.has_value()); | |
| EXPECT_EQ(doubles_opt->get().type()->type_id(), TypeId::kList); | |
| ICEBERG_UNWRAP_OR_FAIL(auto properties_opt, | |
| result.schema->FindFieldByName("properties")); | |
| EXPECT_FALSE(properties_opt.has_value()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto toplevel_opt, result.schema->FindFieldByName("toplevel")); | |
| ASSERT_TRUE(toplevel_opt.has_value()); | |
| EXPECT_EQ(*toplevel_opt->get().type(), *decimal(9, 2)); | |
| EXPECT_TRUE(toplevel_opt->get().optional()); | |
| } | |
| // ============================================================================ | |
| // Move Operations Tests | |
| // ============================================================================ | |
| TEST_F(UpdateSchemaTest, TestMultipleMoves) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| setup->AddColumn("w", int64()); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveFirst("w").MoveFirst("z").MoveAfter("y", "w").MoveBefore("w", "x"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| EXPECT_EQ(fields[0].name(), "z"); | |
| EXPECT_EQ(fields[1].name(), "y"); | |
| int w_pos = -1; | |
| int x_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "w") w_pos = i; | |
| if (fields[i].name() == "x") x_pos = i; | |
| } | |
| EXPECT_GT(w_pos, 0); | |
| EXPECT_GT(x_pos, 0); | |
| EXPECT_LT(w_pos, x_pos); // w should come before x | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopLevelColumnFirst) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->MoveFirst("y"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| EXPECT_EQ(fields[0].name(), "y"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopLevelColumnBeforeFirst) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->MoveBefore("y", "x"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int y_pos = -1; | |
| int x_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "y") y_pos = i; | |
| if (fields[i].name() == "x") x_pos = i; | |
| } | |
| EXPECT_GE(y_pos, 0); | |
| EXPECT_GE(x_pos, 0); | |
| EXPECT_LT(y_pos, x_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopLevelColumnAfterLast) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->MoveAfter("x", "z"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| EXPECT_EQ(fields[fields.size() - 1].name(), "x"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopLevelColumnAfter) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| setup->AddColumn("w", timestamp_tz()); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveAfter("w", "x"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int w_pos = -1; | |
| int x_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "w") w_pos = i; | |
| if (fields[i].name() == "x") x_pos = i; | |
| } | |
| EXPECT_GE(w_pos, 0); | |
| EXPECT_GE(x_pos, 0); | |
| EXPECT_EQ(w_pos, x_pos + 1); // w should be immediately after x | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopLevelColumnBefore) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| setup->AddColumn("w", timestamp_tz()); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("w", "z"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int w_pos = -1; | |
| int z_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "w") w_pos = i; | |
| if (fields[i].name() == "z") z_pos = i; | |
| } | |
| EXPECT_GE(w_pos, 0); | |
| EXPECT_GE(z_pos, 0); | |
| EXPECT_LT(w_pos, z_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveNestedFieldFirst) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(100, "a", int64(), true), SchemaField(101, "b", int64(), true)}); | |
| setup->AddColumn("s", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveFirst("s.b"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto s_opt, result.schema->FindFieldByName("s")); | |
| ASSERT_TRUE(s_opt.has_value()); | |
| const auto& s_struct = checked_cast<const StructType&>(*s_opt->get().type()); | |
| const auto& fields = s_struct.fields(); | |
| EXPECT_EQ(fields[0].name(), "b"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveNestedFieldBeforeFirst) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(100, "a", int64(), true), SchemaField(101, "b", int64(), true)}); | |
| setup->AddColumn("s", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("s.b", "s.a"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto s_opt, result.schema->FindFieldByName("s")); | |
| ASSERT_TRUE(s_opt.has_value()); | |
| const auto& s_struct = checked_cast<const StructType&>(*s_opt->get().type()); | |
| const auto& fields = s_struct.fields(); | |
| int a_pos = -1; | |
| int b_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "a") a_pos = i; | |
| if (fields[i].name() == "b") b_pos = i; | |
| } | |
| EXPECT_GE(a_pos, 0); | |
| EXPECT_GE(b_pos, 0); | |
| EXPECT_LT(b_pos, a_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveNestedFieldAfterLast) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(100, "a", int64(), true), SchemaField(101, "b", int64(), true)}); | |
| setup->AddColumn("s", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveAfter("s.a", "s.b"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto s_opt, result.schema->FindFieldByName("s")); | |
| ASSERT_TRUE(s_opt.has_value()); | |
| const auto& s_struct = checked_cast<const StructType&>(*s_opt->get().type()); | |
| const auto& fields = s_struct.fields(); | |
| EXPECT_EQ(fields[fields.size() - 1].name(), "a"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveNestedFieldAfter) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(100, "a", int64(), true), SchemaField(101, "b", int64(), true), | |
| SchemaField(102, "c", timestamp_tz(), false)}); | |
| setup->AddColumn("s", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveAfter("s.c", "s.a"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto s_opt, result.schema->FindFieldByName("s")); | |
| ASSERT_TRUE(s_opt.has_value()); | |
| const auto& s_struct = checked_cast<const StructType&>(*s_opt->get().type()); | |
| const auto& fields = s_struct.fields(); | |
| int a_pos = -1; | |
| int c_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "a") a_pos = i; | |
| if (fields[i].name() == "c") c_pos = i; | |
| } | |
| EXPECT_GE(a_pos, 0); | |
| EXPECT_GE(c_pos, 0); | |
| EXPECT_EQ(c_pos, a_pos + 1); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveNestedFieldBefore) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(102, "c", timestamp_tz(), false), SchemaField(100, "a", int64(), true), | |
| SchemaField(101, "b", int64(), true)}); | |
| setup->AddColumn("s", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("s.c", "s.b"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto s_opt, result.schema->FindFieldByName("s")); | |
| ASSERT_TRUE(s_opt.has_value()); | |
| const auto& s_struct = checked_cast<const StructType&>(*s_opt->get().type()); | |
| const auto& fields = s_struct.fields(); | |
| int b_pos = -1; | |
| int c_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "b") b_pos = i; | |
| if (fields[i].name() == "c") c_pos = i; | |
| } | |
| EXPECT_GE(b_pos, 0); | |
| EXPECT_GE(c_pos, 0); | |
| EXPECT_LT(c_pos, b_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveListElementField) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto elem_struct = std::make_shared<StructType>(std::vector<SchemaField>{ | |
| SchemaField(100, "a", int64(), true), SchemaField(101, "b", int64(), true)}); | |
| auto list_type = | |
| std::make_shared<ListType>(SchemaField(99, "element", elem_struct, false)); | |
| setup->AddColumn("list", list_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveAfter("list.a", "list.b"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto list_opt, result.schema->FindFieldByName("list")); | |
| ASSERT_TRUE(list_opt.has_value()); | |
| const auto& list_type_result = checked_cast<const ListType&>(*list_opt->get().type()); | |
| const auto& elem_struct_result = | |
| checked_cast<const StructType&>(*list_type_result.element().type()); | |
| const auto& fields = elem_struct_result.fields(); | |
| int a_pos = -1; | |
| int b_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "a") a_pos = i; | |
| if (fields[i].name() == "b") b_pos = i; | |
| } | |
| EXPECT_GE(a_pos, 0); | |
| EXPECT_GE(b_pos, 0); | |
| EXPECT_GT(a_pos, b_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveMapValueStructField) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto value_struct = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "lat", float64(), true), | |
| SchemaField(101, "long", float64(), true)}); | |
| auto map_type = | |
| std::make_shared<MapType>(SchemaField(98, "key", string(), false), | |
| SchemaField(99, "value", value_struct, false)); | |
| setup->AddColumn("locations", map_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveAfter("locations.lat", "locations.long"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto locs_opt, result.schema->FindFieldByName("locations")); | |
| ASSERT_TRUE(locs_opt.has_value()); | |
| const auto& locs_map = checked_cast<const MapType&>(*locs_opt->get().type()); | |
| const auto& value_struct_result = | |
| checked_cast<const StructType&>(*locs_map.value().type()); | |
| const auto& fields = value_struct_result.fields(); | |
| int lat_pos = -1; | |
| int long_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "lat") lat_pos = i; | |
| if (fields[i].name() == "long") long_pos = i; | |
| } | |
| EXPECT_GE(lat_pos, 0); | |
| EXPECT_GE(long_pos, 0); | |
| EXPECT_GT(lat_pos, long_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveAddedTopLevelColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("ts", timestamp_tz()).MoveAfter("ts", "x"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int ts_pos = -1; | |
| int x_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "ts") ts_pos = i; | |
| if (fields[i].name() == "x") x_pos = i; | |
| } | |
| EXPECT_GE(ts_pos, 0); | |
| EXPECT_GE(x_pos, 0); | |
| EXPECT_EQ(ts_pos, x_pos + 1); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveAddedTopLevelColumnAfterAddedColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AddColumn("ts", timestamp_tz()) | |
| .AddColumn("count", int64()) | |
| .MoveAfter("ts", "x") | |
| .MoveAfter("count", "ts"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int x_pos = -1; | |
| int ts_pos = -1; | |
| int count_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "x") x_pos = i; | |
| if (fields[i].name() == "ts") ts_pos = i; | |
| if (fields[i].name() == "count") count_pos = i; | |
| } | |
| EXPECT_GE(x_pos, 0); | |
| EXPECT_GE(ts_pos, 0); | |
| EXPECT_GE(count_pos, 0); | |
| EXPECT_EQ(ts_pos, x_pos + 1); | |
| EXPECT_EQ(count_pos, ts_pos + 1); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveAddedNestedStructField) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "feature1", string(), true), | |
| SchemaField(101, "feature2", string(), true)}); | |
| setup->AddColumn("preferences", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("preferences", "ts", timestamp_tz()) | |
| .MoveBefore("preferences.ts", "preferences.feature1"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto prefs_opt, result.schema->FindFieldByName("preferences")); | |
| ASSERT_TRUE(prefs_opt.has_value()); | |
| const auto& prefs_struct = checked_cast<const StructType&>(*prefs_opt->get().type()); | |
| const auto& fields = prefs_struct.fields(); | |
| EXPECT_EQ(fields[0].name(), "ts"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveAddedNestedStructFieldBeforeAddedColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "feature1", string(), true), | |
| SchemaField(101, "feature2", string(), true)}); | |
| setup->AddColumn("preferences", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AddColumn("preferences", "ts", timestamp_tz()) | |
| .AddColumn("preferences", "size", int64()) | |
| .MoveBefore("preferences.ts", "preferences.feature1") | |
| .MoveBefore("preferences.size", "preferences.ts"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto prefs_opt, result.schema->FindFieldByName("preferences")); | |
| ASSERT_TRUE(prefs_opt.has_value()); | |
| const auto& prefs_struct = checked_cast<const StructType&>(*prefs_opt->get().type()); | |
| const auto& fields = prefs_struct.fields(); | |
| EXPECT_EQ(fields[0].name(), "size"); | |
| EXPECT_EQ(fields[1].name(), "ts"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveSelfReferenceFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update1, table_->NewUpdateSchema()); | |
| update1->MoveBefore("x", "x"); | |
| auto result1 = update1->Apply(); | |
| EXPECT_THAT(result1, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result1, HasErrorMessage("Cannot move x before itself")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update2, table_->NewUpdateSchema()); | |
| update2->MoveAfter("x", "x"); | |
| auto result2 = update2->Apply(); | |
| EXPECT_THAT(result2, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result2, HasErrorMessage("Cannot move x after itself")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveMissingColumnFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update1, table_->NewUpdateSchema()); | |
| update1->MoveFirst("items"); | |
| auto result1 = update1->Apply(); | |
| EXPECT_THAT(result1, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result1, HasErrorMessage("Cannot move missing column: items")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update2, table_->NewUpdateSchema()); | |
| update2->MoveBefore("items", "x"); | |
| auto result2 = update2->Apply(); | |
| EXPECT_THAT(result2, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result2, HasErrorMessage("Cannot move missing column: items")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update3, table_->NewUpdateSchema()); | |
| update3->MoveAfter("items", "y"); | |
| auto result3 = update3->Apply(); | |
| EXPECT_THAT(result3, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result3, HasErrorMessage("Cannot move missing column: items")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveBeforeAddFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->MoveBefore("ts", "x").AddColumn("ts", timestamp_tz()); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot move missing column: ts")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveMissingReferenceColumnFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update1, table_->NewUpdateSchema()); | |
| update1->MoveBefore("x", "items"); | |
| auto result1 = update1->Apply(); | |
| EXPECT_THAT(result1, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result1, HasErrorMessage("Cannot move x before missing column: items")); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update2, table_->NewUpdateSchema()); | |
| update2->MoveAfter("y", "items"); | |
| auto result2 = update2->Apply(); | |
| EXPECT_THAT(result2, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result2, HasErrorMessage("Cannot move y after missing column: items")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMovePrimitiveMapKeyFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto map_type = std::make_shared<MapType>(SchemaField(98, "key", string(), false), | |
| SchemaField(99, "value", string(), false)); | |
| setup->AllowIncompatibleChanges().AddRequiredColumn("properties", map_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("properties.key", "properties.value"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot move fields in non-struct type")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMovePrimitiveMapValueFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto map_type = std::make_shared<MapType>(SchemaField(98, "key", string(), false), | |
| SchemaField(99, "value", string(), false)); | |
| setup->AllowIncompatibleChanges().AddRequiredColumn("properties", map_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("properties.value", "properties.key"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot move fields in non-struct type")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMovePrimitiveListElementFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto list_type = | |
| std::make_shared<ListType>(SchemaField(98, "element", float64(), false)); | |
| setup->AllowIncompatibleChanges().AddRequiredColumn("doubles", list_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("doubles.element", "doubles"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot move fields in non-struct type")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopLevelBetweenStructsFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "feature1", string(), true)}); | |
| setup->AddColumn("preferences", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("x", "preferences.feature1"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, HasErrorMessage("Cannot move field x to a different struct")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveBetweenStructsFails) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct1 = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "a", int64(), true)}); | |
| auto struct2 = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(101, "feature1", string(), true)}); | |
| setup->AddColumn("points", struct1); | |
| setup->AddColumn("preferences", struct2); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->MoveBefore("points.a", "preferences.feature1"); | |
| auto result = update->Apply(); | |
| EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed)); | |
| EXPECT_THAT(result, | |
| HasErrorMessage("Cannot move field points.a to a different struct")); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopDeletedColumnAfterAnotherColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges() | |
| .DeleteColumn("z") | |
| .AddRequiredColumn("z", int32()) | |
| .MoveAfter("z", "y"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int z_pos = -1; | |
| int y_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "z") z_pos = i; | |
| if (fields[i].name() == "y") y_pos = i; | |
| } | |
| EXPECT_GE(z_pos, 0); | |
| EXPECT_GE(y_pos, 0); | |
| EXPECT_GT(z_pos, y_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopDeletedColumnBeforeAnotherColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges() | |
| .DeleteColumn("z") | |
| .AddRequiredColumn("z", int32()) | |
| .MoveBefore("z", "x"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int z_pos = -1; | |
| int x_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "z") z_pos = i; | |
| if (fields[i].name() == "x") x_pos = i; | |
| } | |
| EXPECT_GE(z_pos, 0); | |
| EXPECT_GE(x_pos, 0); | |
| EXPECT_LT(z_pos, x_pos); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveTopDeletedColumnToFirst) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges() | |
| .DeleteColumn("z") | |
| .AddRequiredColumn("z", int32()) | |
| .MoveFirst("z"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| EXPECT_EQ(fields[0].name(), "z"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveDeletedNestedStructFieldAfterAnotherColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "feature1", string(), true), | |
| SchemaField(101, "feature2", string(), true)}); | |
| setup->AddColumn("preferences", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges() | |
| .DeleteColumn("preferences.feature1") | |
| .AddRequiredColumn("preferences", "feature1", boolean()) | |
| .MoveAfter("preferences.feature1", "preferences.feature2"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto prefs_opt, result.schema->FindFieldByName("preferences")); | |
| ASSERT_TRUE(prefs_opt.has_value()); | |
| const auto& prefs_struct = checked_cast<const StructType&>(*prefs_opt->get().type()); | |
| const auto& fields = prefs_struct.fields(); | |
| EXPECT_EQ(fields[fields.size() - 1].name(), "feature1"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveDeletedNestedStructFieldBeforeAnotherColumn) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "feature1", string(), true), | |
| SchemaField(101, "feature2", string(), true)}); | |
| setup->AddColumn("preferences", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges() | |
| .DeleteColumn("preferences.feature2") | |
| .AddColumn("preferences", "feature2", boolean()) | |
| .MoveBefore("preferences.feature2", "preferences.feature1"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto prefs_opt, result.schema->FindFieldByName("preferences")); | |
| ASSERT_TRUE(prefs_opt.has_value()); | |
| const auto& prefs_struct = checked_cast<const StructType&>(*prefs_opt->get().type()); | |
| const auto& fields = prefs_struct.fields(); | |
| EXPECT_EQ(fields[0].name(), "feature2"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestMoveDeletedNestedStructFieldToFirst) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "feature1", string(), true), | |
| SchemaField(101, "feature2", string(), true)}); | |
| setup->AddColumn("preferences", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->AllowIncompatibleChanges() | |
| .DeleteColumn("preferences.feature2") | |
| .AddColumn("preferences", "feature2", boolean()) | |
| .MoveFirst("preferences.feature2"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto prefs_opt, result.schema->FindFieldByName("preferences")); | |
| ASSERT_TRUE(prefs_opt.has_value()); | |
| const auto& prefs_struct = checked_cast<const StructType&>(*prefs_opt->get().type()); | |
| const auto& fields = prefs_struct.fields(); | |
| EXPECT_EQ(fields[0].name(), "feature2"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestCaseInsensitiveAddTopLevelAndMove) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(false).AddColumn("TS", timestamp_tz()).MoveAfter("ts", "X"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int ts_pos = -1; | |
| int x_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "TS") ts_pos = i; | |
| if (fields[i].name() == "x") x_pos = i; | |
| } | |
| EXPECT_GE(ts_pos, 0); | |
| EXPECT_GE(x_pos, 0); | |
| EXPECT_EQ(ts_pos, x_pos + 1); | |
| } | |
| TEST_F(UpdateSchemaTest, TestCaseInsensitiveAddNestedAndMove) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto setup, table_->NewUpdateSchema()); | |
| auto struct_type = std::make_shared<StructType>( | |
| std::vector<SchemaField>{SchemaField(100, "Feature1", string(), true)}); | |
| setup->AddColumn("Preferences", struct_type); | |
| EXPECT_THAT(setup->Commit(), IsOk()); | |
| ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_)); | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, reloaded->NewUpdateSchema()); | |
| update->CaseSensitive(false) | |
| .AddColumn("Preferences", "TS", timestamp_tz()) | |
| .MoveBefore("preferences.ts", "PREFERENCES.Feature1"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| ICEBERG_UNWRAP_OR_FAIL(auto prefs_opt, result.schema->FindFieldByName("Preferences")); | |
| ASSERT_TRUE(prefs_opt.has_value()); | |
| const auto& prefs_struct = checked_cast<const StructType&>(*prefs_opt->get().type()); | |
| const auto& fields = prefs_struct.fields(); | |
| EXPECT_EQ(fields[0].name(), "TS"); | |
| } | |
| TEST_F(UpdateSchemaTest, TestCaseInsensitiveMoveAfterNewlyAddedField) { | |
| ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateSchema()); | |
| update->CaseSensitive(false) | |
| .AddColumn("TS", timestamp_tz()) | |
| .AddColumn("Count", int64()) | |
| .MoveAfter("ts", "X") | |
| .MoveAfter("count", "TS"); | |
| ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply()); | |
| ASSERT_TRUE(result.schema != nullptr); | |
| const auto& fields = result.schema->fields(); | |
| int x_pos = -1; | |
| int ts_pos = -1; | |
| int count_pos = -1; | |
| for (size_t i = 0; i < fields.size(); ++i) { | |
| if (fields[i].name() == "x") x_pos = i; | |
| if (fields[i].name() == "TS") ts_pos = i; | |
| if (fields[i].name() == "Count") count_pos = i; | |
| } | |
| EXPECT_GE(x_pos, 0); | |
| EXPECT_GE(ts_pos, 0); | |
| EXPECT_GE(count_pos, 0); | |
| EXPECT_EQ(ts_pos, x_pos + 1); | |
| EXPECT_EQ(count_pos, ts_pos + 1); | |
| } | |
| } // namespace iceberg |