-
Notifications
You must be signed in to change notification settings - Fork 481
[lake/paimon] Compare paimon schema and Fluss schema before alter table. #2331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |||||||||||||
| import org.apache.fluss.annotation.PublicEvolving; | ||||||||||||||
| import org.apache.fluss.exception.TableAlreadyExistException; | ||||||||||||||
| import org.apache.fluss.exception.TableNotExistException; | ||||||||||||||
| import org.apache.fluss.metadata.Schema; | ||||||||||||||
| import org.apache.fluss.metadata.TableChange; | ||||||||||||||
| import org.apache.fluss.metadata.TableDescriptor; | ||||||||||||||
| import org.apache.fluss.metadata.TablePath; | ||||||||||||||
|
|
@@ -84,5 +85,12 @@ interface Context { | |||||||||||||
|
|
||||||||||||||
| /** Get the fluss principal currently accessing the catalog. */ | ||||||||||||||
| FlussPrincipal getFlussPrincipal(); | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Get the current schema of fluss. | ||||||||||||||
| * | ||||||||||||||
| * @return the current schema of fluss, | ||||||||||||||
|
Comment on lines
+90
to
+92
|
||||||||||||||
| * Get the current schema of fluss. | |
| * | |
| * @return the current schema of fluss, | |
| * Get the current schema of Fluss. | |
| * | |
| * @return the current schema of Fluss. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,13 +37,16 @@ | |||||||||||||||||
| import org.apache.paimon.schema.SchemaChange; | ||||||||||||||||||
| import org.apache.paimon.table.FileStoreTable; | ||||||||||||||||||
| import org.apache.paimon.table.Table; | ||||||||||||||||||
| import org.apache.paimon.types.DataField; | ||||||||||||||||||
| import org.apache.paimon.types.DataType; | ||||||||||||||||||
| import org.apache.paimon.types.DataTypes; | ||||||||||||||||||
| import org.slf4j.Logger; | ||||||||||||||||||
| import org.slf4j.LoggerFactory; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.util.Collections; | ||||||||||||||||||
| import java.util.LinkedHashMap; | ||||||||||||||||||
| import java.util.List; | ||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||
|
|
||||||||||||||||||
| import static org.apache.fluss.lake.paimon.utils.PaimonConversions.toPaimon; | ||||||||||||||||||
| import static org.apache.fluss.lake.paimon.utils.PaimonConversions.toPaimonSchema; | ||||||||||||||||||
|
|
@@ -113,9 +116,11 @@ public void alterTable(TablePath tablePath, List<TableChange> tableChanges, Cont | |||||||||||||||||
| throws TableNotExistException { | ||||||||||||||||||
| try { | ||||||||||||||||||
| List<SchemaChange> paimonSchemaChanges = toPaimonSchemaChanges(tableChanges); | ||||||||||||||||||
|
|
||||||||||||||||||
| org.apache.fluss.metadata.Schema flussSchema = context.getFlussSchema(); | ||||||||||||||||||
| List<TableChange> remainingChanges = | ||||||||||||||||||
| checkAndFilterDuplicateTableChanges(tablePath, tableChanges, flussSchema); | ||||||||||||||||||
| // Compare current Paimon table schema with expected target schema before altering | ||||||||||||||||||
| if (shouldAlterTable(tablePath, tableChanges)) { | ||||||||||||||||||
| if (!remainingChanges.isEmpty()) { | ||||||||||||||||||
| alterTable(tablePath, paimonSchemaChanges); | ||||||||||||||||||
| } else { | ||||||||||||||||||
| // If schemas already match, treat as idempotent success | ||||||||||||||||||
|
|
@@ -133,69 +138,118 @@ public void alterTable(TablePath tablePath, List<TableChange> tableChanges, Cont | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private boolean shouldAlterTable(TablePath tablePath, List<TableChange> tableChanges) | ||||||||||||||||||
| private List<TableChange> checkAndFilterDuplicateTableChanges( | ||||||||||||||||||
| TablePath tablePath, | ||||||||||||||||||
| List<TableChange> tableChanges, | ||||||||||||||||||
| org.apache.fluss.metadata.Schema flussSchema) | ||||||||||||||||||
| throws TableNotExistException { | ||||||||||||||||||
| if (tableChanges.isEmpty()) { | ||||||||||||||||||
| return tableChanges; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| try { | ||||||||||||||||||
| // Get current Paimon table schema | ||||||||||||||||||
| Table table = paimonCatalog.getTable(toPaimon(tablePath)); | ||||||||||||||||||
| FileStoreTable fileStoreTable = (FileStoreTable) table; | ||||||||||||||||||
| Schema currentSchema = fileStoreTable.schema().toSchema(); | ||||||||||||||||||
|
|
||||||||||||||||||
| for (TableChange change : tableChanges) { | ||||||||||||||||||
| if (change instanceof TableChange.AddColumn) { | ||||||||||||||||||
| TableChange.AddColumn addColumn = (TableChange.AddColumn) change; | ||||||||||||||||||
| if (!isColumnAlreadyExists(currentSchema, addColumn)) { | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
| } else { | ||||||||||||||||||
| return true; | ||||||||||||||||||
| List<DataField> paimonFields = | ||||||||||||||||||
| fileStoreTable.schema().toSchema().fields().stream() | ||||||||||||||||||
| .filter(field -> !SYSTEM_COLUMNS.containsKey(field.name())) | ||||||||||||||||||
| .collect(Collectors.toList()); | ||||||||||||||||||
| List<org.apache.fluss.metadata.Schema.Column> flussColumns = flussSchema.getColumns(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (paimonFields.size() < flussColumns.size()) { | ||||||||||||||||||
| throw new InvalidAlterTableException( | ||||||||||||||||||
| String.format( | ||||||||||||||||||
| "Paimon table has less columns (%d) than Fluss schema (%d)", | ||||||||||||||||||
| paimonFields.size(), flussColumns.size())); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Validate schema compatibility | ||||||||||||||||||
|
||||||||||||||||||
| // Validate schema compatibility | |
| // Validate schema compatibility |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment states "meaning last add columns are failed" which should be "meaning the last add columns operation failed" for better grammar. Also, "This time must be retried" could be clearer as "This operation must retry adding the same columns".
| // if paimon column size is greater than expected fluss column size, meaning last add | |
| // columns are failed. | |
| // Thus, this time must be retried to keep the schema same, only then can add new | |
| // columns or other operations next time. | |
| // If Paimon column size is greater than the expected Fluss column size, it means the | |
| // last add columns operation failed. | |
| // Thus, this operation must retry adding the same columns to keep the schemas the | |
| // same before adding new columns or performing other operations. |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message has a grammatical issue. "thus need to add" should be "thus needs to add" or better yet, rephrase to "therefore you need to add the diff columns all at once, rather than applying other table changes" for better clarity.
| "Paimon table has more columns (%d) than Fluss schema (%d), thus need to add the diff columns at once rather than other table changes %s.", | |
| "Paimon table has more columns (%d) than Fluss schema (%d); therefore you need to add the diff columns all at once, rather than applying other table changes: %s.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could perform this check earlier—it’s not related to the existing Paimon schema. Also, it’s already validated here:
fluss/fluss-server/src/main/java/org/apache/fluss/server/coordinator/SchemaUpdate.java
Lines 101 to 104 in 256bd7f
| TableChange.ColumnPosition position = addColumn.getPosition(); | |
| if (position != TableChange.ColumnPosition.last()) { | |
| throw new IllegalArgumentException("Only support addColumn column at last now."); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could split the checks and provide distinct error messages to help users more easily identify issues related to the Paimon schema.
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method checkAndFilterDuplicateTableChanges only handles TableChange.AddColumn operations in its retry logic (lines 186-196). If the method is called with property changes (like TableChange.SetOption or TableChange.ResetOption) when Paimon has more columns than Fluss, it will incorrectly throw an error at line 194. Consider checking if all tableChanges are AddColumn operations before entering the retry logic, or handle non-AddColumn changes appropriately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new method
getFlussSchema()should include a@sinceannotation to document when it was added to the API, consistent with other methods in the interface that have@sinceannotations.