Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions src/Storages/ObjectStorage/DataLakes/Iceberg/IcebergMetadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -828,10 +828,35 @@ void IcebergMetadata::checkAlterIsPossible(const AlterCommands & commands)
ErrorCodes::NOT_IMPLEMENTED,
"Removing column property '{}' from column '{}' is not supported by Iceberg storage", command.to_remove, command.column_name);

if (command.type == AlterCommand::Type::MODIFY_COLUMN && !command.data_type)
if (command.type == AlterCommand::Type::ADD_COLUMN || command.type == AlterCommand::Type::MODIFY_COLUMN)
{
/// The Iceberg schema records only the type and the field order, so any other clause would be dropped.
const char * unsupported_property = nullptr;
if (command.comment)
unsupported_property = "comment";
else if (command.default_expression)
unsupported_property = "default expression";
else if (command.codec)
unsupported_property = "codec";
else if (command.ttl)
unsupported_property = "TTL";
else if (!command.settings_changes.empty() || !command.settings_resets.empty())
unsupported_property = "settings";

if (unsupported_property)
throw Exception(
ErrorCodes::NOT_IMPLEMENTED,
"{} the {} of column '{}' is not supported by Iceberg storage",
command.type == AlterCommand::Type::ADD_COLUMN ? "Setting" : "Changing",
unsupported_property,
command.column_name);
}

if (command.type == AlterCommand::Type::MODIFY_COLUMN && !command.data_type
&& !command.first && command.after_column.empty())
throw Exception(
ErrorCodes::NOT_IMPLEMENTED,
"Modifying column '{}' without changing its type is not supported by Iceberg storage", command.column_name);
"Modifying column '{}' without changing its type or position is not supported by Iceberg storage", command.column_name);
}
}

Expand Down
314 changes: 223 additions & 91 deletions src/Storages/ObjectStorage/DataLakes/Iceberg/MetadataGenerator.cpp

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions src/Storages/ObjectStorage/DataLakes/Iceberg/MetadataGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,31 @@ class MetadataGenerator
std::optional<Int64> user_defined_timestamp = std::nullopt,
bool is_truncate = false);

void generateAddColumnMetadata(const String & column_name, DataTypePtr type, bool first = false, const String & after_column = {});
/// Create a manifest-only rewrite snapshot (`replace` operation) carrying `total-*` counters forward so `OPTIMIZE ... MANIFEST` is idempotent.
NextMetadataResult generateManifestOnlySnapshot(
FileNamesGenerator & generator,
const Iceberg::IcebergPathFromMetadata & metadata_file_path,
Int64 parent_snapshot_id);

void generateAddColumnMetadata(const String & column_name, DataTypePtr type);
void generateDropColumnMetadata(const String & column_name);
/// Returns false when the column already has the requested type (no metadata change).
/// Returns false when neither the type nor the position changed (true no-op).
/// Throws when the requested type differs from the current one but maps to the same Iceberg type.
/// `context` supplies the settings used to map the stored Iceberg type back to a ClickHouse
/// type (the timestamptz timezone and whether geo types are allowed).
bool generateModifyColumnMetadata(const String & column_name, DataTypePtr type, ContextPtr context);
bool generateModifyColumnMetadata(const String & column_name, DataTypePtr type, ContextPtr context, bool first = false, const String & after_column = {});
void generateRenameColumnMetadata(const String & column_name, const String & new_column_name);

/// A commit attempt can land in the catalog even when the client observes a failure
/// (the Iceberg "commit state unknown" case, e.g. a proxy returning 5xx after the catalog
/// applied the update). These predicates let a retry detect that the requested change is
/// already present instead of applying it a second time and failing.
bool isAddColumnApplied(const String & column_name, DataTypePtr type) const;
/// `first` and `after_column` are checked against the field order, as for `isModifyColumnApplied`.
bool isAddColumnApplied(const String & column_name, DataTypePtr type, bool first = false, const String & after_column = {}) const;
bool isDropColumnApplied(const String & column_name) const;
bool isRenameColumnApplied(const String & column_name, const String & new_column_name) const;
bool isModifyColumnApplied(const String & column_name, DataTypePtr type) const;
/// `type` may be null for a position-only `MODIFY COLUMN`; `first` and `after_column` are checked against the field order.
bool isModifyColumnApplied(const String & column_name, DataTypePtr type, bool first = false, const String & after_column = {}) const;

private:
Poco::JSON::Object::Ptr metadata_object;
Expand Down
8 changes: 4 additions & 4 deletions src/Storages/ObjectStorage/DataLakes/Iceberg/Mutations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ static bool alterAlreadyApplied(const MetadataGenerator & generator, const Alter
switch (command.type)
{
case AlterCommand::Type::ADD_COLUMN:
return generator.isAddColumnApplied(command.column_name, command.data_type);
return generator.isAddColumnApplied(command.column_name, command.data_type, command.first, command.after_column);
case AlterCommand::Type::DROP_COLUMN:
return generator.isDropColumnApplied(command.column_name);
case AlterCommand::Type::RENAME_COLUMN:
return generator.isRenameColumnApplied(command.column_name, command.rename_to);
case AlterCommand::Type::MODIFY_COLUMN:
return generator.isModifyColumnApplied(command.column_name, command.data_type);
return generator.isModifyColumnApplied(command.column_name, command.data_type, command.first, command.after_column);
default:
return false;
}
Expand Down Expand Up @@ -900,7 +900,7 @@ void alter(
switch (params[0].type)
{
case AlterCommand::Type::ADD_COLUMN:
metadata_json_generator.generateAddColumnMetadata(params[0].column_name, params[0].data_type);
metadata_json_generator.generateAddColumnMetadata(params[0].column_name, params[0].data_type, params[0].first, params[0].after_column);
break;
case AlterCommand::Type::DROP_COLUMN:
if (params[0].clear)
Expand All @@ -909,7 +909,7 @@ void alter(
break;
case AlterCommand::Type::MODIFY_COLUMN:
{
if (!metadata_json_generator.generateModifyColumnMetadata(params[0].column_name, params[0].data_type, context))
if (!metadata_json_generator.generateModifyColumnMetadata(params[0].column_name, params[0].data_type, context, params[0].first, params[0].after_column))
{
succeeded = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,11 @@ DataTypePtr IcebergSchemaProcessor::getFieldType(
throw Exception(ErrorCodes::BAD_ARGUMENTS, "Unexpected 'type' field: {}", type.toString());
}

DataTypePtr IcebergSchemaProcessor::getClickHouseFieldType(const Poco::JSON::Object::Ptr & field, ContextPtr context_)
{
return getFieldType(field, f_type, context_, field->getValue<bool>(f_required));
}

/**
* Iceberg allows only three types of primitive type conversion:
* int -> long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ class IcebergSchemaProcessor : private WithContext

static DataTypePtr getSimpleType(const String & type_name, ContextPtr context_, bool allow_geo_parser = true);

/// The ClickHouse type the reader derives for a top-level schema field.
DataTypePtr getClickHouseFieldType(const Poco::JSON::Object::Ptr & field, ContextPtr context_);

static std::unordered_map<String, Int64> traverseSchema(Poco::JSON::Array::Ptr schema);

void registerSnapshotWithSchemaId(Int64 snapshot_id, Int32 schema_id);
Expand Down
Loading
Loading