This is an automated email from the ASF dual-hosted git repository.

rpuch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new c7f234ba77 IGNITE-20966 Improve error messages for schema change 
validation failures (#3805)
c7f234ba77 is described below

commit c7f234ba77d039011b1531890be546926fb13bbb
Author: Phillippko <[email protected]>
AuthorDate: Tue May 28 14:39:43 2024 +0400

    IGNITE-20966 Improve error messages for schema change validation failures 
(#3805)
---
 .../ItSchemaForwardCompatibilityTest.java          |  19 ++-
 .../schemasync/ItSchemaSyncSingleNodeTest.java     |  14 +-
 .../replicator/CompatValidationResult.java         |  56 ++++--
 .../replicator/IncompatibleSchemaException.java    |  43 +++++
 .../replicator/PartitionReplicaListener.java       |  19 ++-
 .../replicator/SchemaCompatibilityValidator.java   | 153 +++++++++++------
 .../distributed/schema/ColumnDefinitionDiff.java   |   4 +
 .../replication/PartitionReplicaListenerTest.java  |  26 ++-
 .../SchemaCompatibilityValidatorTest.java          | 187 +++++++++++++++------
 9 files changed, 373 insertions(+), 148 deletions(-)

diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
index d8409314a9..47d2e0054e 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaForwardCompatibilityTest.java
@@ -107,8 +107,10 @@ class ItSchemaForwardCompatibilityTest extends 
ClusterPerTestIntegrationTest {
         assertThat(
                 ex.getMessage(),
                 containsString(String.format(
-                        "Commit failed because schema 1 is not 
forward-compatible with 2 for table %d",
-                        tableId
+                        "Commit failed because schema is not 
forward-compatible [fromSchemaVersion=1, toSchemaVersion=2, table=%s, "
+                                + "details=%s]",
+                        table.name(),
+                        ddl.expectedDetails
                 ))
         );
 
@@ -154,15 +156,18 @@ class ItSchemaForwardCompatibilityTest extends 
ClusterPerTestIntegrationTest {
     private enum ForwardIncompatibleDdl {
         // TODO: Enable after 
https://issues.apache.org/jira/browse/IGNITE-19484 is fixed.
         // RENAME_TABLE("RENAME TABLE " + TABLE_NAME + " to new_table"),
-        DROP_COLUMN("ALTER TABLE " + TABLE_NAME + " DROP COLUMN not_null_int"),
-        ADD_DEFAULT("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN not_null_int 
SET DEFAULT 102"),
-        CHANGE_DEFAULT("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN 
int_with_default SET DEFAULT 102"),
-        DROP_DEFAULT("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN 
int_with_default DROP DEFAULT");
+        DROP_COLUMN("ALTER TABLE " + TABLE_NAME + " DROP COLUMN not_null_int", 
"Columns were dropped"),
+        ADD_DEFAULT("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN not_null_int 
SET DEFAULT 102", "Column default value changed"),
+        CHANGE_DEFAULT("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN 
int_with_default SET DEFAULT 102",
+                "Column default value changed"),
+        DROP_DEFAULT("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN 
int_with_default DROP DEFAULT", "Column default value changed");
 
         private final String ddl;
+        private final String expectedDetails;
 
-        ForwardIncompatibleDdl(String ddl) {
+        ForwardIncompatibleDdl(String ddl, String expectedDetails) {
             this.ddl = ddl;
+            this.expectedDetails = expectedDetails;
         }
 
         void executeOn(Cluster cluster) {
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
index 87d12e8601..cb37e8f382 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/schemasync/ItSchemaSyncSingleNodeTest.java
@@ -110,7 +110,7 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
                     ex.getMessage(),
                     containsString(String.format(
                             "Table schema was updated after the transaction 
was started [table=%s, startSchema=1, operationSchema=2]",
-                            tableId
+                            table.name()
                     ))
             );
         } else {
@@ -119,7 +119,7 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
                     ex.getMessage(),
                     is(String.format(
                             "Table schema was updated after the transaction 
was started [table=%s, startSchema=1, operationSchema=2]",
-                            tableId
+                            table.name()
                     ))
             );
         }
@@ -252,13 +252,15 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
             ex = assertThrows(IgniteException.class, () -> 
operation.execute(table, tx, cluster));
             assertThat(
                     ex.getMessage(),
-                    containsString(String.format("Table was dropped 
[table=%s]", tableId))
+                    // TODO https://issues.apache.org/jira/browse/IGNITE-22309 
use tableName instead
+                    containsString(String.format("Table was dropped 
[tableId=%s]", tableId))
             );
         } else {
             ex = assertThrows(IncompatibleSchemaException.class, () -> 
operation.execute(table, tx, cluster));
             assertThat(
                     ex.getMessage(),
-                    is(String.format("Table was dropped [table=%s]", tableId))
+                    // TODO https://issues.apache.org/jira/browse/IGNITE-22309 
use tableName instead
+                    is(String.format("Table was dropped [tableId=%s]", 
tableId))
             );
         }
 
@@ -294,7 +296,7 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
         assertThat(ex, is(instanceOf(TransactionException.class)));
         assertThat(
                 ex.getMessage(),
-                containsString(String.format("Commit failed because a table 
was already dropped [tableId=%s]", tableId))
+                containsString(String.format("Commit failed because a table 
was already dropped [table=%s]", table.name()))
         );
 
         assertThat(((TransactionException) ex).code(), 
is(Transactions.TX_UNEXPECTED_STATE_ERR));
@@ -356,7 +358,7 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
                 containsString(String.format(
                         "Operation failed because it tried to access a row 
with newer schema version than transaction's [table=%s, "
                                 + "txSchemaVersion=1, rowSchemaVersion=2]",
-                        tableId
+                        table.name()
                 ))
         );
 
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/CompatValidationResult.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/CompatValidationResult.java
index d4a00ed5d3..984ac4730c 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/CompatValidationResult.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/CompatValidationResult.java
@@ -23,16 +23,17 @@ import org.jetbrains.annotations.Nullable;
  * Result of a schema compatibility validation.
  */
 public class CompatValidationResult {
-    private static final CompatValidationResult SUCCESSFUL_RESULT = new 
CompatValidationResult(ValidationStatus.SUCCESS, -1, -1, -1);
+    private static final CompatValidationResult SUCCESSFUL_RESULT = new 
CompatValidationResult(ValidationStatus.SUCCESS, "", -1, -1, null);
 
     private enum ValidationStatus {
         SUCCESS, INCOMPATIBLE_CHANGE, TABLE_DROPPED
     }
 
     private final ValidationStatus status;
-    private final int failedTableId;
+    private final String failedTableName;
     private final int fromSchemaVersion;
     private final @Nullable Integer toSchemaVersion;
+    private final @Nullable String details;
 
     /**
      * Returns a successful validation result.
@@ -46,36 +47,49 @@ public class CompatValidationResult {
     /**
      * Creates a validation result denoting incompatible schema change.
      *
-     * @param failedTableId Table which schema change is incompatible.
+     * @param failedTableName Table which schema change is incompatible.
      * @param fromSchemaVersion Version number of the schema from which an 
incompatible transition tried to be made.
      * @param toSchemaVersion Version number of the schema to which an 
incompatible transition tried to be made.
      * @return A validation result for a failure.
      */
-    public static CompatValidationResult incompatibleChange(int failedTableId, 
int fromSchemaVersion, int toSchemaVersion) {
-        return new 
CompatValidationResult(ValidationStatus.INCOMPATIBLE_CHANGE, failedTableId, 
fromSchemaVersion, toSchemaVersion);
+    public static CompatValidationResult incompatibleChange(
+            String failedTableName,
+            int fromSchemaVersion,
+            int toSchemaVersion,
+            @Nullable String details
+    ) {
+        return new CompatValidationResult(
+                ValidationStatus.INCOMPATIBLE_CHANGE,
+                failedTableName,
+                fromSchemaVersion,
+                toSchemaVersion,
+                details
+        );
     }
 
     /**
      * Creates a validation result denoting 'table already dropped when commit 
is made' situation.
      *
-     * @param failedTableId Table which schema change is incompatible.
+     * @param failedTableName Table which schema change is incompatible.
      * @param fromSchemaVersion Version number of the schema from which an 
incompatible transition tried to be made.
      * @return A validation result for a failure.
      */
-    public static CompatValidationResult tableDropped(int failedTableId, int 
fromSchemaVersion) {
-        return new CompatValidationResult(ValidationStatus.TABLE_DROPPED, 
failedTableId, fromSchemaVersion, null);
+    public static CompatValidationResult tableDropped(String failedTableName, 
int fromSchemaVersion) {
+        return new CompatValidationResult(ValidationStatus.TABLE_DROPPED, 
failedTableName, fromSchemaVersion, null, null);
     }
 
     private CompatValidationResult(
             ValidationStatus status,
-            int failedTableId,
+            String failedTableName,
             int fromSchemaVersion,
-            @Nullable Integer toSchemaVersion
+            @Nullable Integer toSchemaVersion,
+            @Nullable String details
     ) {
         this.status = status;
-        this.failedTableId = failedTableId;
+        this.failedTableName = failedTableName;
         this.fromSchemaVersion = fromSchemaVersion;
         this.toSchemaVersion = toSchemaVersion;
+        this.details = details;
     }
 
     /**
@@ -95,15 +109,15 @@ public class CompatValidationResult {
     }
 
     /**
-     * Returns ID of the table for which the validation has failed. Should 
only be called for a failed validation result, otherwise an
+     * Returns name of the table for which the validation has failed. Should 
only be called for a failed validation result, otherwise an
      * exception is thrown.
      *
-     * @return Table ID.
+     * @return Table name.
      */
-    public int failedTableId() {
+    public String failedTableName() {
         assert !isSuccessful() : "Should not be called on a successful result";
 
-        return failedTableId;
+        return failedTableName;
     }
 
     /**
@@ -128,4 +142,16 @@ public class CompatValidationResult {
 
         return toSchemaVersion;
     }
+
+    /**
+     * For failed validation returns details why the operation failed.
+     *
+     * @return Details why the operation failed.
+     */
+    public String details() {
+        assert !isSuccessful() : "Should not be called on a successful result";
+        assert details != null : "Should not be called when there are no 
details";
+
+        return details;
+    }
 }
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
index 04480f784c..5c4f0027f8 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
@@ -26,7 +26,50 @@ import org.apache.ignite.tx.TransactionException;
  * because an incompatible schema change has happened.
  */
 public class IncompatibleSchemaException extends TransactionException 
implements ExpectedReplicationException {
+    private static final String SCHEMA_CHANGED_MESSAGE = "Table schema was 
updated after the transaction was started "
+            + "[table=%s, startSchema=%d, operationSchema=%d]";
+
+    private static final String TABLE_DROPPED_NAME_MESSAGE = "Table was 
dropped [table=%s]";
+
+    private static final String TABLE_DROPPED_ID_MESSAGE = "Table was dropped 
[tableId=%d]";
+
     public IncompatibleSchemaException(String message) {
         super(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR, message);
     }
+
+    /**
+     * Returns new IncompatibleSchemaException for a case when schema was 
updated after the beginning of the transaction.
+     *
+     * @param tableName Name of the table.
+     * @param startSchemaVersion Schema version at the beginning of the 
transaction.
+     * @param operationSchemaVersion Schema version at the moment of the 
operation.
+     * @return Exception with formatted message.
+     */
+    public static IncompatibleSchemaException schemaChanged(String tableName, 
int startSchemaVersion, int operationSchemaVersion) {
+        return new IncompatibleSchemaException(String.format(
+                SCHEMA_CHANGED_MESSAGE,
+                tableName, startSchemaVersion, operationSchemaVersion
+        ));
+    }
+
+    /**
+     * Returns new IncompatibleSchemaException for a case when the table was 
dropped at the moment of operation.
+     *
+     * @param tableName Name of the table.
+     * @return Exception with formatted message.
+     */
+    public static IncompatibleSchemaException tableDropped(String tableName) {
+        return new 
IncompatibleSchemaException(String.format(TABLE_DROPPED_NAME_MESSAGE, 
tableName));
+    }
+
+    /**
+     * Returns new IncompatibleSchemaException for a case when table was 
dropped at the moment of operation.
+     *
+     * @param tableId ID of the table.
+     * @return Exception with formatted message.
+     */
+    // TODO https://issues.apache.org/jira/browse/IGNITE-22309 use tableName 
instead
+    public static IncompatibleSchemaException tableDropped(int tableId) {
+        return new 
IncompatibleSchemaException(String.format(TABLE_DROPPED_ID_MESSAGE, tableId));
+    }
 }
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
index 7113ee5b68..f2b1e092b7 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/PartitionReplicaListener.java
@@ -1026,9 +1026,9 @@ public class PartitionReplicaListener implements 
ReplicaListener {
                 .thenAccept(validationResult -> {
                     if (!validationResult.isSuccessful()) {
                         throw new IncompatibleSchemaException(String.format(
-                                "Operation failed because it tried to access a 
row with newer schema version than transaction's [table=%d, "
+                                "Operation failed because it tried to access a 
row with newer schema version than transaction's [table=%s, "
                                         + "txSchemaVersion=%d, 
rowSchemaVersion=%d]",
-                                validationResult.failedTableId(), 
validationResult.fromSchemaVersion(), validationResult.toSchemaVersion()
+                                validationResult.failedTableName(), 
validationResult.fromSchemaVersion(), validationResult.toSchemaVersion()
                         ));
                     }
                 });
@@ -1620,17 +1620,20 @@ public class PartitionReplicaListener implements 
ReplicaListener {
     private static void 
throwIfSchemaValidationOnCommitFailed(CompatValidationResult validationResult, 
TransactionResult txResult) {
         if (!validationResult.isSuccessful()) {
             if (validationResult.isTableDropped()) {
-                // TODO: IGNITE-20966 - improve error message.
                 throw new MismatchingTransactionOutcomeException(
-                        format("Commit failed because a table was already 
dropped [tableId={}]", validationResult.failedTableId()),
+                        format("Commit failed because a table was already 
dropped [table={}]", validationResult.failedTableName()),
                         txResult
                 );
             } else {
-                // TODO: IGNITE-20966 - improve error message.
                 throw new MismatchingTransactionOutcomeException(
-                        "Commit failed because schema "
-                                + validationResult.fromSchemaVersion() + " is 
not forward-compatible with "
-                                + validationResult.toSchemaVersion() + " for 
table " + validationResult.failedTableId(),
+                        format(
+                                "Commit failed because schema is not 
forward-compatible "
+                                        + "[fromSchemaVersion={}, 
toSchemaVersion={}, table={}, details={}]",
+                                validationResult.fromSchemaVersion(),
+                                validationResult.toSchemaVersion(),
+                                validationResult.failedTableName(),
+                                validationResult.details()
+                        ),
                         txResult
                 );
             }
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidator.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidator.java
index c90a8239e7..303a2b8d2a 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidator.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidator.java
@@ -37,6 +37,7 @@ import 
org.apache.ignite.internal.table.distributed.schema.SchemaSyncService;
 import org.apache.ignite.internal.table.distributed.schema.TableDefinitionDiff;
 import 
org.apache.ignite.internal.table.distributed.schema.ValidationSchemasSource;
 import org.apache.ignite.internal.tx.TransactionIds;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Validates schema compatibility.
@@ -116,7 +117,7 @@ class SchemaCompatibilityValidator {
             CatalogTableDescriptor tableAtTxStart = 
catalogService.table(tableId, beginTimestamp.longValue());
             assert tableAtTxStart != null : "No table " + tableId + " at ts " 
+ beginTimestamp;
 
-            return CompatValidationResult.tableDropped(tableId, 
tableAtTxStart.schemaId());
+            return CompatValidationResult.tableDropped(tableAtTxStart.name(), 
tableAtTxStart.schemaId());
         }
 
         return validateForwardSchemaCompatibility(beginTimestamp, 
commitTimestamp, tableId);
@@ -144,15 +145,23 @@ class SchemaCompatibilityValidator {
         for (int i = 0; i < tableSchemas.size() - 1; i++) {
             FullTableSchema oldSchema = tableSchemas.get(i);
             FullTableSchema newSchema = tableSchemas.get(i + 1);
-            if (!isForwardCompatible(oldSchema, newSchema)) {
-                return CompatValidationResult.incompatibleChange(tableId, 
oldSchema.schemaVersion(), newSchema.schemaVersion());
+
+            ValidationResult validationResult = 
validateForwardSchemaCompatibility(oldSchema, newSchema);
+
+            if (validationResult.verdict == ValidatorVerdict.INCOMPATIBLE) {
+                return CompatValidationResult.incompatibleChange(
+                        oldSchema.tableName(),
+                        oldSchema.schemaVersion(),
+                        newSchema.schemaVersion(),
+                        validationResult.details()
+                );
             }
         }
 
         return CompatValidationResult.success();
     }
 
-    private boolean isForwardCompatible(FullTableSchema prevSchema, 
FullTableSchema nextSchema) {
+    private ValidationResult 
validateForwardSchemaCompatibility(FullTableSchema prevSchema, FullTableSchema 
nextSchema) {
         TableDefinitionDiff diff = diffCache.computeIfAbsent(
                 new TableDefinitionDiffKey(prevSchema.tableId(), 
prevSchema.schemaVersion(), nextSchema.schemaVersion()),
                 key -> nextSchema.diffFrom(prevSchema)
@@ -161,21 +170,23 @@ class SchemaCompatibilityValidator {
         boolean accepted = false;
 
         for (ForwardCompatibilityValidator validator : 
FORWARD_COMPATIBILITY_VALIDATORS) {
-            switch (validator.compatible(diff)) {
+            ValidationResult validationResult = validator.compatible(diff);
+            switch (validationResult.verdict) {
                 case COMPATIBLE:
                     accepted = true;
                     break;
                 case INCOMPATIBLE:
-                    return false;
+                    return validationResult;
                 default:
                     break;
             }
         }
 
-        assert accepted : "Table schema changed from " + 
prevSchema.schemaVersion() + " and " + nextSchema.schemaVersion()
+        assert accepted : "Table schema changed from " + 
prevSchema.schemaVersion()
+                + " to " + nextSchema.schemaVersion()
                 + ", but no schema change validator voted for any change. Some 
schema validator is missing.";
 
-        return true;
+        return ValidationResult.COMPATIBLE;
     }
 
     /**
@@ -219,7 +230,12 @@ class SchemaCompatibilityValidator {
 
         FullTableSchema oldSchema = tableSchemas.get(0);
         FullTableSchema newSchema = tableSchemas.get(1);
-        return CompatValidationResult.incompatibleChange(tableId, 
oldSchema.schemaVersion(), newSchema.schemaVersion());
+        return CompatValidationResult.incompatibleChange(
+                oldSchema.tableName(),
+                oldSchema.schemaVersion(),
+                newSchema.schemaVersion(),
+                null
+        );
     }
 
     void failIfSchemaChangedAfterTxStart(UUID txId, HybridTimestamp 
operationTimestamp, int tableId) {
@@ -230,28 +246,23 @@ class SchemaCompatibilityValidator {
         assert tableAtBeginTs != null;
 
         if (tableAtOpTs == null) {
-            throw tableWasDroppedException(tableId);
+            throw 
IncompatibleSchemaException.tableDropped(tableAtBeginTs.name());
         }
 
         if (tableAtOpTs.tableVersion() != tableAtBeginTs.tableVersion()) {
-            throw new IncompatibleSchemaException(
-                    String.format(
-                            "Table schema was updated after the transaction 
was started [table=%d, startSchema=%d, operationSchema=%d]",
-                            tableId, tableAtBeginTs.tableVersion(), 
tableAtOpTs.tableVersion()
-                    )
+            throw IncompatibleSchemaException.schemaChanged(
+                    tableAtBeginTs.name(),
+                    tableAtBeginTs.tableVersion(),
+                    tableAtOpTs.tableVersion()
             );
         }
     }
 
-    private static IncompatibleSchemaException tableWasDroppedException(int 
tableId) {
-        return new IncompatibleSchemaException(String.format("Table was 
dropped [table=%d]", tableId));
-    }
-
     void failIfTableDoesNotExistAt(HybridTimestamp operationTimestamp, int 
tableId) {
         CatalogTableDescriptor tableAtOpTs = catalogService.table(tableId, 
operationTimestamp.longValue());
 
         if (tableAtOpTs == null) {
-            throw tableWasDroppedException(tableId);
+            throw IncompatibleSchemaException.tableDropped(tableId);
         }
     }
 
@@ -274,6 +285,27 @@ class SchemaCompatibilityValidator {
         }
     }
 
+    private static class ValidationResult {
+        private static final ValidationResult COMPATIBLE = new 
ValidationResult(ValidatorVerdict.COMPATIBLE, null);
+        private static final ValidationResult DONT_CARE = new 
ValidationResult(ValidatorVerdict.DONT_CARE, null);
+
+        private final ValidatorVerdict verdict;
+        private final String details;
+
+        ValidationResult(ValidatorVerdict verdict, @Nullable String details) {
+            this.verdict = verdict;
+            this.details = details;
+        }
+
+        ValidatorVerdict verdict() {
+            return verdict;
+        }
+
+        @Nullable String details() {
+            return details;
+        }
+    }
+
     private enum ValidatorVerdict {
         /**
          * Validator accepts a change: it's compatible.
@@ -291,47 +323,58 @@ class SchemaCompatibilityValidator {
 
     @SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
     private interface ForwardCompatibilityValidator {
-        ValidatorVerdict compatible(TableDefinitionDiff diff);
+        ValidationResult compatible(TableDefinitionDiff diff);
     }
 
     private static class RenameTableValidator implements 
ForwardCompatibilityValidator {
+        private static final ValidationResult INCOMPATIBLE = new 
ValidationResult(
+                ValidatorVerdict.INCOMPATIBLE,
+                "Name of the table has been changed"
+        );
+
         @Override
-        public ValidatorVerdict compatible(TableDefinitionDiff diff) {
-            return diff.nameDiffers() ? ValidatorVerdict.INCOMPATIBLE : 
ValidatorVerdict.DONT_CARE;
+        public ValidationResult compatible(TableDefinitionDiff diff) {
+            return diff.nameDiffers() ? INCOMPATIBLE : 
ValidationResult.DONT_CARE;
         }
     }
 
     private static class AddColumnsValidator implements 
ForwardCompatibilityValidator {
+
         @Override
-        public ValidatorVerdict compatible(TableDefinitionDiff diff) {
+        public ValidationResult compatible(TableDefinitionDiff diff) {
             if (diff.addedColumns().isEmpty()) {
-                return ValidatorVerdict.DONT_CARE;
+                return ValidationResult.DONT_CARE;
             }
 
             for (CatalogTableColumnDescriptor column : diff.addedColumns()) {
                 if (!column.nullable() && column.defaultValue() == null) {
-                    return ValidatorVerdict.INCOMPATIBLE;
+                    return new ValidationResult(ValidatorVerdict.INCOMPATIBLE, 
"Not null column added without default value");
                 }
             }
 
-            return ValidatorVerdict.COMPATIBLE;
+            return ValidationResult.COMPATIBLE;
         }
     }
 
     private static class DropColumnsValidator implements 
ForwardCompatibilityValidator {
+        private static final ValidationResult INCOMPATIBLE = new 
ValidationResult(
+                ValidatorVerdict.INCOMPATIBLE,
+                "Columns were dropped"
+        );
+
         @Override
-        public ValidatorVerdict compatible(TableDefinitionDiff diff) {
-            return diff.removedColumns().isEmpty() ? 
ValidatorVerdict.DONT_CARE : ValidatorVerdict.INCOMPATIBLE;
+        public ValidationResult compatible(TableDefinitionDiff diff) {
+            return diff.removedColumns().isEmpty() ? 
ValidationResult.DONT_CARE : INCOMPATIBLE;
         }
     }
 
     @SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
     private interface ColumnChangeCompatibilityValidator {
-        ValidatorVerdict compatible(ColumnDefinitionDiff diff);
+        ValidationResult compatible(ColumnDefinitionDiff diff);
     }
 
     private static class ChangeColumnsValidator implements 
ForwardCompatibilityValidator {
-        private final List<ColumnChangeCompatibilityValidator> validators = 
List.of(
+        private static final List<ColumnChangeCompatibilityValidator> 
validators = List.of(
                 // TODO: https://issues.apache.org/jira/browse/IGNITE-20948 - 
add validator that says that column rename is compatible.
                 new ChangeNullabilityValidator(),
                 new ChangeDefaultValueValidator(),
@@ -339,81 +382,89 @@ class SchemaCompatibilityValidator {
         );
 
         @Override
-        public ValidatorVerdict compatible(TableDefinitionDiff diff) {
+        public ValidationResult compatible(TableDefinitionDiff diff) {
             if (diff.changedColumns().isEmpty()) {
-                return ValidatorVerdict.DONT_CARE;
+                return ValidationResult.DONT_CARE;
             }
 
             boolean accepted = false;
 
             for (ColumnDefinitionDiff columnDiff : diff.changedColumns()) {
-                switch (compatible(columnDiff)) {
+                ValidationResult validationResult = compatible(columnDiff);
+                switch (validationResult.verdict()) {
                     case COMPATIBLE:
                         accepted = true;
                         break;
                     case INCOMPATIBLE:
-                        return ValidatorVerdict.INCOMPATIBLE;
+                        return validationResult;
                     default:
                         break;
                 }
             }
 
-            assert accepted : "Table schema changed from " + 
diff.oldSchemaVersion() + " and " + diff.newSchemaVersion()
-                    + ", but no column change validator voted for any change. 
Some schema validator is missing.";
+            assert accepted : "Table schema changed from " + 
diff.oldSchemaVersion() + " to "
+                    + diff.newSchemaVersion() + ", but no column change 
validator voted for any change. Some schema validator is missing.";
 
-            return ValidatorVerdict.COMPATIBLE;
+            return ValidationResult.COMPATIBLE;
         }
 
-        private ValidatorVerdict compatible(ColumnDefinitionDiff columnDiff) {
+        private static ValidationResult compatible(ColumnDefinitionDiff 
columnDiff) {
             boolean accepted = false;
 
             for (ColumnChangeCompatibilityValidator validator : validators) {
-                switch (validator.compatible(columnDiff)) {
+                ValidationResult validationResult = 
validator.compatible(columnDiff);
+
+                switch (validationResult.verdict()) {
                     case COMPATIBLE:
                         accepted = true;
                         break;
                     case INCOMPATIBLE:
-                        return ValidatorVerdict.INCOMPATIBLE;
+                        return validationResult;
                     default:
                         break;
                 }
             }
 
-            return accepted ? ValidatorVerdict.COMPATIBLE : 
ValidatorVerdict.DONT_CARE;
+            return accepted ? ValidationResult.COMPATIBLE : 
ValidationResult.DONT_CARE;
         }
     }
 
     private static class ChangeDefaultValueValidator implements 
ColumnChangeCompatibilityValidator {
         @Override
-        public ValidatorVerdict compatible(ColumnDefinitionDiff diff) {
-            return diff.defaultChanged() ? ValidatorVerdict.INCOMPATIBLE : 
ValidatorVerdict.DONT_CARE;
+        public ValidationResult compatible(ColumnDefinitionDiff diff) {
+            return diff.defaultChanged()
+                    ? new ValidationResult(ValidatorVerdict.INCOMPATIBLE, 
"Column default value changed")
+                    : ValidationResult.DONT_CARE;
         }
     }
 
     private static class ChangeNullabilityValidator implements 
ColumnChangeCompatibilityValidator {
         @Override
-        public ValidatorVerdict compatible(ColumnDefinitionDiff diff) {
+        public ValidationResult compatible(ColumnDefinitionDiff diff) {
             if (diff.notNullAdded()) {
-                return ValidatorVerdict.INCOMPATIBLE;
+                return new ValidationResult(ValidatorVerdict.INCOMPATIBLE, 
"Not null added");
             }
+
             if (diff.notNullDropped()) {
-                return ValidatorVerdict.COMPATIBLE;
+                return ValidationResult.COMPATIBLE;
             }
 
             assert !diff.nullabilityChanged() : diff;
 
-            return ValidatorVerdict.DONT_CARE;
+            return ValidationResult.DONT_CARE;
         }
     }
 
     private static class ChangeColumnTypeValidator implements 
ColumnChangeCompatibilityValidator {
         @Override
-        public ValidatorVerdict compatible(ColumnDefinitionDiff diff) {
+        public ValidationResult compatible(ColumnDefinitionDiff diff) {
             if (!diff.typeChanged()) {
-                return ValidatorVerdict.DONT_CARE;
+                return ValidationResult.DONT_CARE;
             }
 
-            return diff.typeChangeIsSupported() ? ValidatorVerdict.COMPATIBLE 
: ValidatorVerdict.INCOMPATIBLE;
+            return diff.typeChangeIsSupported()
+                    ? ValidationResult.COMPATIBLE
+                    : new ValidationResult(ValidatorVerdict.INCOMPATIBLE, 
"Column type changed incompatibly");
         }
     }
 }
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/schema/ColumnDefinitionDiff.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/schema/ColumnDefinitionDiff.java
index 0590ad4f2e..8dbe4af988 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/schema/ColumnDefinitionDiff.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/schema/ColumnDefinitionDiff.java
@@ -77,4 +77,8 @@ public class ColumnDefinitionDiff {
     public boolean defaultChanged() {
         return !Objects.equals(oldColumn.defaultValue(), 
newColumn.defaultValue());
     }
+
+    public String oldName() {
+        return oldColumn.name();
+    }
 }
diff --git 
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
 
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
index fec5a93771..015285ba95 100644
--- 
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
+++ 
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
@@ -260,6 +260,8 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
     private static final int ANOTHER_TABLE_ID = 2;
 
     private static final long ANY_ENLISTMENT_CONSISTENCY_TOKEN = 1L;
+    private static final String TABLE_NAME = "test";
+    private static final String TABLE_NAME_2 = "second_test";
 
     private final Map<UUID, Set<RowId>> pendingRows = new 
ConcurrentHashMap<>();
 
@@ -382,7 +384,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
     private KvMarshaller<TestKey, TestValue> kvMarshallerVersion2;
 
     private final CatalogTableDescriptor tableDescriptor = new 
CatalogTableDescriptor(
-            TABLE_ID, 1, 2, "table", 1,
+            TABLE_ID, 1, 2, TABLE_NAME, 1,
             List.of(
                     new CatalogTableColumnDescriptor("intKey", 
ColumnType.INT32, false, 0, 0, 0, null),
                     new CatalogTableColumnDescriptor("strKey", 
ColumnType.STRING, false, 0, 0, 0, null),
@@ -1712,7 +1714,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
     }
 
     private static FullTableSchema tableSchema(int schemaVersion, 
List<CatalogTableColumnDescriptor> columns) {
-        return new FullTableSchema(schemaVersion, TABLE_ID, "test", columns);
+        return new FullTableSchema(schemaVersion, TABLE_ID, TABLE_NAME, 
columns);
     }
 
     private AtomicReference<Boolean> interceptFinishTxCommand() {
@@ -1777,7 +1779,8 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
         MismatchingTransactionOutcomeException ex = assertWillThrowFast(future,
                 MismatchingTransactionOutcomeException.class);
 
-        assertThat(ex.getMessage(), containsString("Commit failed because 
schema 1 is not forward-compatible with 2"));
+        assertThat(ex.getMessage(), containsString("Commit failed because 
schema is not forward-compatible [fromSchemaVersion=1, "
+                + "toSchemaVersion=2, table=test, details=Column default value 
changed]"));
 
         assertThat(committed.get(), is(false));
     }
@@ -1874,7 +1877,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
                 IncompatibleSchemaException.class);
         assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
         assertThat(ex.getMessage(), containsString(
-                "Operation failed because it tried to access a row with newer 
schema version than transaction's [table=1, "
+                "Operation failed because it tried to access a row with newer 
schema version than transaction's [table=test, "
                         + "txSchemaVersion=1, rowSchemaVersion=2]"
         ));
 
@@ -2208,7 +2211,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
             assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
             assertThat(
                     ex.getMessage(),
-                    is("Table schema was updated after the transaction was 
started [table=1, startSchema=1, operationSchema=2]")
+                    is("Table schema was updated after the transaction was 
started [table=test, startSchema=1, operationSchema=2]")
             );
         } else {
             assertThat(future, willCompleteSuccessfully());
@@ -2218,6 +2221,9 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
     private void makeSchemaChangeAfter(HybridTimestamp txBeginTs) {
         CatalogTableDescriptor tableVersion1 = 
mock(CatalogTableDescriptor.class);
         CatalogTableDescriptor tableVersion2 = 
mock(CatalogTableDescriptor.class);
+
+        when(tableVersion1.name()).thenReturn(TABLE_NAME);
+        when(tableVersion2.name()).thenReturn(TABLE_NAME_2);
         when(tableVersion1.tableVersion()).thenReturn(CURRENT_SCHEMA_VERSION);
         when(tableVersion2.tableVersion()).thenReturn(NEXT_SCHEMA_VERSION);
 
@@ -2323,7 +2329,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
 
         IncompatibleSchemaException ex = assertWillThrowFast(future, 
IncompatibleSchemaException.class);
         assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
-        assertThat(ex.getMessage(), is("Table was dropped [table=1]"));
+        assertThat(ex.getMessage(), is("Table was dropped [tableId=1]"));
     }
 
     private void makeTableBeDroppedAfter(HybridTimestamp txBeginTs) {
@@ -2333,6 +2339,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
     private void makeTableBeDroppedAfter(HybridTimestamp txBeginTs, int 
tableId) {
         CatalogTableDescriptor tableVersion1 = 
mock(CatalogTableDescriptor.class);
         when(tableVersion1.tableVersion()).thenReturn(CURRENT_SCHEMA_VERSION);
+        when(tableVersion1.name()).thenReturn(TABLE_NAME);
 
         when(catalogService.table(tableId, 
txBeginTs.longValue())).thenReturn(tableVersion1);
         when(catalogService.table(eq(tableId), 
gt(txBeginTs.longValue()))).thenReturn(null);
@@ -2420,7 +2427,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
 
         IncompatibleSchemaException ex = assertWillThrowFast(future, 
IncompatibleSchemaException.class);
         assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
-        assertThat(ex.getMessage(), is("Table was dropped [table=1]"));
+        assertThat(ex.getMessage(), is("Table was dropped [tableId=1]"));
     }
 
     @CartesianTest
@@ -2473,6 +2480,8 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
         UUID txId = newTxId();
         HybridTimestamp txBeginTs = beginTimestamp(txId);
 
+        String tableNameToBeDropped = catalogService.table(tableToBeDroppedId, 
txBeginTs.longValue()).name();
+
         makeTableBeDroppedAfter(txBeginTs, tableToBeDroppedId);
 
         CompletableFuture<?> future = partitionReplicaListener.invoke(
@@ -2489,7 +2498,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
 
         MismatchingTransactionOutcomeException ex = 
assertWillThrowFast(future, MismatchingTransactionOutcomeException.class);
 
-        assertThat(ex.getMessage(), is("Commit failed because a table was 
already dropped [tableId=" + tableToBeDroppedId + "]"));
+        assertThat(ex.getMessage(), is("Commit failed because a table was 
already dropped [table=" + tableNameToBeDropped + "]"));
 
         assertThat("The transaction must have been aborted", committed.get(), 
is(false));
     }
@@ -2529,6 +2538,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
     private void makeSchemaBeNextVersion() {
         CatalogTableDescriptor tableVersion2 = 
mock(CatalogTableDescriptor.class);
         when(tableVersion2.tableVersion()).thenReturn(NEXT_SCHEMA_VERSION);
+        when(tableVersion2.name()).thenReturn(TABLE_NAME_2);
 
         when(catalogService.table(eq(TABLE_ID), 
anyLong())).thenReturn(tableVersion2);
     }
diff --git 
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidatorTest.java
 
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidatorTest.java
index f9e30397e6..c7a1509697 100644
--- 
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidatorTest.java
+++ 
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replicator/SchemaCompatibilityValidatorTest.java
@@ -69,6 +69,7 @@ import 
org.apache.ignite.internal.table.distributed.schema.ValidationSchemasSour
 import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
 import org.apache.ignite.internal.tx.TransactionIds;
 import org.apache.ignite.sql.ColumnType;
+import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -148,9 +149,10 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
         assertThat("Change is compatible", result.isSuccessful(), is(false));
         assertThat(result.isTableDropped(), is(false));
 
-        assertThat(result.failedTableId(), is(TABLE_ID));
+        assertThat(result.failedTableName(), is(TABLE_NAME));
         assertThat(result.fromSchemaVersion(), is(1));
         assertThat(result.toSchemaVersion(), is(2));
+        assertThat(result.details(), is(changeSource.expectedDetails()));
     }
 
     @ParameterizedTest
@@ -300,7 +302,7 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
 
     @Test
     void combinationOfForwardCompatibleChangesIsCompatible() {
-        assertForwardCompatibleChangeAllowsCommitting(() -> List.of(
+        
assertForwardCompatibleChangeAllowsCommitting((CompatibleSchemaChangeSource) () 
-> List.of(
                 tableSchema(1, List.of(column(INT32, false))),
                 // Type is widened, NOT NULL dropped.
                 tableSchema(2, List.of(column(INT64, true)))
@@ -321,11 +323,21 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
 
     @Test
     void oneForwardIncompatibleChangeMakesCombinationIncompatible() {
-        assertForwardIncompatibleChangeDisallowsCommitting(() -> List.of(
-                tableSchema(1, List.of(column(INT32, true))),
-                // Type is widened (compatible), but NOT NULL added 
(incompatible).
-                tableSchema(2, List.of(column(INT64, false)))
-        ));
+        assertForwardIncompatibleChangeDisallowsCommitting(new 
SchemaChangeSource() {
+            @Override
+            public List<FullTableSchema> schemaVersions() {
+                return List.of(
+                        tableSchema(1, List.of(column(INT32, true))),
+                        // Type is widened (compatible), but NOT NULL added 
(incompatible).
+                        tableSchema(2, List.of(column(INT64, false)))
+                );
+            }
+
+            @Override
+            public String expectedDetails() {
+                return "Not null added";
+            }
+        });
     }
 
     @Test
@@ -340,7 +352,7 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
 
     @Test
     void changeOppositeToForwardCompatibleChangeIsNotBackwardCompatible() {
-        assertChangeIsNotBackwardCompatible(() -> List.of(
+        assertChangeIsNotBackwardCompatible((CompatibleSchemaChangeSource) () 
-> List.of(
                 tableSchema(1, List.of(
                         intColumn("col1"),
                         intColumnWithDefault("col2", 42)
@@ -366,7 +378,7 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
         assertThat("Change is compatible", result.isSuccessful(), is(false));
         assertThat(result.isTableDropped(), is(false));
 
-        assertThat(result.failedTableId(), is(TABLE_ID));
+        assertThat(result.failedTableName(), is(TABLE_NAME));
         assertThat(result.fromSchemaVersion(), is(1));
         assertThat(result.toSchemaVersion(), is(2));
     }
@@ -383,6 +395,18 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
         );
     }
 
+    private static CatalogTableColumnDescriptor int64Column(String columnName) 
{
+        return new CatalogTableColumnDescriptor(
+                columnName,
+                INT64,
+                false,
+                DEFAULT_PRECISION,
+                DEFAULT_SCALE,
+                DEFAULT_LENGTH,
+                null
+        );
+    }
+
     private static CatalogTableColumnDescriptor nullableIntColumn(String 
columnName) {
         return new CatalogTableColumnDescriptor(columnName, INT32, true, 
DEFAULT_PRECISION, DEFAULT_SCALE, DEFAULT_LENGTH, null);
     }
@@ -407,12 +431,21 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
         return new FullTableSchema(schemaVersion, TABLE_ID, name, columns);
     }
 
-    @FunctionalInterface
     private interface SchemaChangeSource {
         List<FullTableSchema> schemaVersions();
+
+        String expectedDetails();
     }
 
-    private enum ForwardCompatibleChange implements SchemaChangeSource {
+    @FunctionalInterface
+    private interface CompatibleSchemaChangeSource extends SchemaChangeSource {
+        @Override
+        default @Nullable String expectedDetails() {
+            return null;
+        }
+    }
+
+    private enum ForwardCompatibleChange implements 
CompatibleSchemaChangeSource {
         ADD_NULLABLE_COLUMN(List.of(
                 tableSchema(1, List.of(
                         intColumn("col1")
@@ -462,58 +495,101 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
     }
 
     private enum ForwardIncompatibleChange implements SchemaChangeSource {
-        RENAME_TABLE(List.of(
-                tableSchema(1, TABLE_NAME, List.of(
-                        intColumn("col1")
-                )),
-                tableSchema(2, ANOTHER_NAME, List.of(
-                        intColumn("col1")
-                ))
-        )),
-        DROP_COLUMN(List.of(
-                tableSchema(1, List.of(
-                        intColumn("col1"),
-                        intColumn("col2")
-                )),
-                tableSchema(2, List.of(
-                        intColumn("col1")
-                ))
-        )),
-        ADD_DEFAULT(List.of(
-                tableSchema(1, List.of(
-                        intColumn("col1")
-                )),
-                tableSchema(2, List.of(
-                        intColumnWithDefault("col1", 42)
-                ))
-        )),
-        CHANGE_DEFAULT(List.of(
-                tableSchema(1, List.of(
-                        intColumnWithDefault("col1", 1)
-                )),
-                tableSchema(2, List.of(
-                        intColumnWithDefault("col1", 2)
-                ))
-        )),
-        DROP_DEFAULT(List.of(
-                tableSchema(1, List.of(
-                        intColumnWithDefault("col1", 42)
-                )),
-                tableSchema(2, List.of(
-                        intColumn("col1")
-                ))
-        ));
+        RENAME_TABLE(
+                List.of(
+                        tableSchema(1, TABLE_NAME, List.of(
+                                intColumn("col1")
+                        )),
+                        tableSchema(2, ANOTHER_NAME, List.of(
+                                intColumn("col1")
+                        ))
+                ),
+                "Name of the table has been changed"
+        ),
+        DROP_COLUMN(
+                List.of(
+                        tableSchema(1, List.of(
+                                intColumn("col1"),
+                                intColumn("col2")
+                        )),
+                        tableSchema(2, List.of(
+                                intColumn("col1")
+                        ))
+                ),
+                "Columns were dropped"
+        ),
+        ADD_DEFAULT(
+                List.of(
+                        tableSchema(1, List.of(
+                                intColumn("col1")
+                        )),
+                        tableSchema(2, List.of(
+                                intColumnWithDefault("col1", 42)
+                        ))
+                ),
+                "Column default value changed"
+        ),
+        CHANGE_DEFAULT(
+                List.of(
+                        tableSchema(1, List.of(
+                                intColumnWithDefault("col1", 1)
+                        )),
+                        tableSchema(2, List.of(
+                                intColumnWithDefault("col1", 2)
+                        ))
+                ),
+                "Column default value changed"
+        ),
+        DROP_DEFAULT(
+                List.of(
+                        tableSchema(1, List.of(
+                                intColumnWithDefault("col1", 42)
+                        )),
+                        tableSchema(2, List.of(
+                                intColumn("col1")
+                        ))
+                ),
+                "Column default value changed"
+        ),
+        NON_NULL_WITHOUT_DEFAULT(
+                List.of(
+                        tableSchema(1, List.of(
+                        )),
+                        tableSchema(2, List.of(
+                                intColumn("NON_NULL_WITHOUT_DEFAULT_COL")
+                        ))
+                ),
+                "Not null column added without default value"
+        ),
+        TYPE_CHANGED(
+                List.of(
+                        tableSchema(1, List.of(
+                                int64Column("TYPE_CHANGED_COL")
+                        )),
+                        tableSchema(2, List.of(
+                                intColumn("TYPE_CHANGED_COL")
+                        ))
+                ),
+                "Column type changed incompatibly"
+        );
 
         private final List<FullTableSchema> schemaVersions;
+        private final String expectedDetails;
 
-        ForwardIncompatibleChange(List<FullTableSchema> schemaVersions) {
+        ForwardIncompatibleChange(List<FullTableSchema> schemaVersions, String 
expectedDetails) {
             this.schemaVersions = schemaVersions;
+            this.expectedDetails = expectedDetails;
         }
 
         @Override
         public List<FullTableSchema> schemaVersions() {
             return schemaVersions;
         }
+
+        @Override
+        public String expectedDetails() {
+            return expectedDetails;
+        }
     }
 
     private static class Type {
@@ -574,6 +650,11 @@ class SchemaCompatibilityValidatorTest extends 
BaseIgniteAbstractTest {
             );
         }
 
+        @Override
+        public String expectedDetails() {
+            return "Column type changed incompatibly";
+        }
+
         private static CatalogTableColumnDescriptor columnFromType(Type type) {
             return new CatalogTableColumnDescriptor(
                     "col",

Reply via email to