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 0b803ee01b IGNITE-20415 Internal IncompatibleSchemaException is thrown 
from public API (#4068)
0b803ee01b is described below

commit 0b803ee01b7ef84ecbdac145ce24f5530218888b
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Mon Jul 22 19:06:13 2024 +0400

    IGNITE-20415 Internal IncompatibleSchemaException is thrown from public API 
(#4068)
---
 .../org/apache/ignite/lang/IgniteException.java    |  4 +-
 .../ignite/tx/IncompatibleSchemaException.java}    | 26 ++++++-----
 .../tx/MismatchingTransactionOutcomeException.java | 37 ++++++---------
 .../org/apache/ignite/tx/TransactionException.java | 11 +++--
 .../ItSchemaForwardCompatibilityTest.java          | 50 +++++++++++++++-----
 .../schemasync/ItSchemaSyncSingleNodeTest.java     | 24 +++-------
 .../ignite/internal/table/ItDurableFinishTest.java |  7 +--
 .../internal/table/ItTransactionRecoveryTest.java  |  8 ++--
 .../ignite/internal/table/AbstractTableView.java   |  4 +-
 .../table/TableExceptionMapperProvider.java}       | 17 +++----
 ...ava => IncompatibleSchemaVersionException.java} | 19 ++++----
 .../replicator/PartitionReplicaListener.java       | 13 +++---
 .../replicator/SchemaCompatibilityValidator.java   |  6 +--
 .../StaleTransactionOperationException.java        |  2 +-
 .../table/TableExceptionMapperProviderTest.java    | 54 ++++++++++++++++++++++
 .../replication/PartitionReplicaListenerTest.java  | 19 ++++----
 .../tx/IncompatibleSchemaAbortException.java}      | 25 ++++------
 ...tchingTransactionOutcomeInternalException.java} | 14 ++++--
 .../tx/TransactionExceptionMapperProvider.java     |  6 +++
 .../internal/tx/impl/TransactionInflights.java     |  4 +-
 .../ignite/internal/tx/impl/TxManagerImpl.java     | 11 +++--
 .../apache/ignite/internal/tx/TxManagerTest.java   | 13 +++---
 22 files changed, 220 insertions(+), 154 deletions(-)

diff --git 
a/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java 
b/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java
index 218941be99..3c3fcd9eb5 100644
--- a/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java
+++ b/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java
@@ -143,7 +143,7 @@ public class IgniteException extends RuntimeException 
implements TraceableExcept
      * @param code Full error code.
      * @param cause Optional nested exception (can be {@code null}).
      */
-    public IgniteException(int code, Throwable cause) {
+    public IgniteException(int code, @Nullable Throwable cause) {
         this(getOrCreateTraceId(cause), code, cause);
     }
 
@@ -154,7 +154,7 @@ public class IgniteException extends RuntimeException 
implements TraceableExcept
      * @param code Full error code.
      * @param cause Optional nested exception (can be {@code null}).
      */
-    public IgniteException(UUID traceId, int code, Throwable cause) {
+    public IgniteException(UUID traceId, int code, @Nullable Throwable cause) {
         super((cause != null) ? cause.getLocalizedMessage() : null, cause);
 
         this.traceId = traceId;
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
 
b/modules/api/src/main/java/org/apache/ignite/tx/IncompatibleSchemaException.java
similarity index 52%
copy from 
modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
copy to 
modules/api/src/main/java/org/apache/ignite/tx/IncompatibleSchemaException.java
index 1dab412af1..8625b0e56c 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
+++ 
b/modules/api/src/main/java/org/apache/ignite/tx/IncompatibleSchemaException.java
@@ -15,23 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.table.distributed.replicator;
-
-import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
-import static 
org.apache.ignite.lang.ErrorGroups.Transactions.TX_STALE_OPERATION_ERR;
+package org.apache.ignite.tx;
 
 import java.util.UUID;
-import org.apache.ignite.internal.tx.TransactionInternalException;
+import org.jetbrains.annotations.Nullable;
 
-/** Error that occurs when a stale operation of a completed transaction is 
detected. */
-// TODO: IGNITE-20415 - make this exception public.
-public class StaleTransactionOperationException extends 
TransactionInternalException {
+/**
+ * Thrown when, during an attempt to execute a transactional operation, it 
turns out that the operation cannot be executed
+ * because an incompatible schema change has happened. The transaction in 
question is aborted.
+ */
+public class IncompatibleSchemaException extends TransactionException {
     /**
-     * Constructor.
+     * Constructs a new instance of IncompatibleSchemaException.
      *
-     * @param txId Transaction ID.
+     * @param traceId Trace ID.
+     * @param code Error code.
+     * @param message Error message.
+     * @param cause The Throwable that is the cause of this exception (can be 
{@code null}).
      */
-    public StaleTransactionOperationException(UUID txId) {
-        super(TX_STALE_OPERATION_ERR, format("Stale operation of a completed 
transaction was detected: [txId={}]", txId));
+    public IncompatibleSchemaException(UUID traceId, int code, String message, 
@Nullable Throwable cause) {
+        super(traceId, code, message, cause);
     }
 }
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
 
b/modules/api/src/main/java/org/apache/ignite/tx/MismatchingTransactionOutcomeException.java
similarity index 50%
copy from 
modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
copy to 
modules/api/src/main/java/org/apache/ignite/tx/MismatchingTransactionOutcomeException.java
index 6fc15f87c7..88a942f1d9 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
+++ 
b/modules/api/src/main/java/org/apache/ignite/tx/MismatchingTransactionOutcomeException.java
@@ -15,38 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.tx;
+package org.apache.ignite.tx;
 
-import static 
org.apache.ignite.lang.ErrorGroups.Transactions.TX_UNEXPECTED_STATE_ERR;
+import java.util.UUID;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * The exception is thrown when the transaction result differs from the 
intended one.
  *
  * <p>For example, {@code tx.commit()} is called for a transaction, but the 
verification logic decided to abort it instead. The transaction
- * will be finished with {@link TxState#ABORTED} and the call to {@code 
tx.commit()} will throw this exception.
+ * will be aborted and the call to {@code tx.commit()} will throw this 
exception.</p>
  */
-// TODO: IGNITE-20415 - split this into public exception (in a public package) 
and internal exception (carrying internal state).
-public class MismatchingTransactionOutcomeException extends 
TransactionInternalException {
-
-    private static final long serialVersionUID = -7953057695915339651L;
-
-    /** Stored transaction result. */
-    private final TransactionResult transactionResult;
-
+public class MismatchingTransactionOutcomeException extends 
TransactionException {
     /**
-     * Constructor.
+     * Constructs a new instance.
+     *
+     * @param traceId Trace ID.
+     * @param code Full error code.
+     * @param message Error message.
+     * @param cause The Throwable that is the cause of this exception (can be 
{@code null}).
      */
-    public MismatchingTransactionOutcomeException(int errorCode, String 
message, TransactionResult transactionResult, Throwable cause) {
-        super(errorCode, message, cause);
-
-        this.transactionResult = transactionResult;
-    }
-
-    public MismatchingTransactionOutcomeException(String message, 
TransactionResult transactionResult) {
-        this(TX_UNEXPECTED_STATE_ERR, message, transactionResult, null);
-    }
-
-    public TransactionResult transactionResult() {
-        return transactionResult;
+    public MismatchingTransactionOutcomeException(UUID traceId, int code, 
String message, @Nullable Throwable cause) {
+        super(traceId, code, message, cause);
     }
 }
diff --git 
a/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java 
b/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java
index 49895f2737..d1f2581b88 100644
--- a/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java
+++ b/modules/api/src/main/java/org/apache/ignite/tx/TransactionException.java
@@ -19,8 +19,9 @@ package org.apache.ignite.tx;
 
 import java.util.UUID;
 import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
 
-/** This exception is thrown if a transaction can't be finished by some 
reasons. */
+/** This exception is thrown if a transaction can't be finished for some 
reason. */
 public class TransactionException extends IgniteException {
     /**
      * Creates a new transaction exception with a message.
@@ -48,7 +49,7 @@ public class TransactionException extends IgniteException {
      * @param code Full error code.
      * @param cause Optional nested exception (can be {@code null}).
      */
-    public TransactionException(int code, Throwable cause) {
+    public TransactionException(int code, @Nullable Throwable cause) {
         super(code, cause);
     }
 
@@ -70,7 +71,7 @@ public class TransactionException extends IgniteException {
      * @param code Full error code.
      * @param cause Optional nested exception (can be {@code null}).
      */
-    public TransactionException(UUID traceId, int code, Throwable cause) {
+    public TransactionException(UUID traceId, int code, @Nullable Throwable 
cause) {
         super(traceId, code, cause);
     }
 
@@ -81,7 +82,7 @@ public class TransactionException extends IgniteException {
      * @param message Detail message.
      * @param cause Optional nested exception (can be {@code null}).
      */
-    public TransactionException(int code, String message, Throwable cause) {
+    public TransactionException(int code, String message, @Nullable Throwable 
cause) {
         super(code, message, cause);
     }
 
@@ -93,7 +94,7 @@ public class TransactionException extends IgniteException {
      * @param message Detail message.
      * @param cause Optional nested exception (can be {@code null}).
      */
-    public TransactionException(UUID traceId, int code, String message, 
Throwable cause) {
+    public TransactionException(UUID traceId, int code, String message, 
@Nullable Throwable cause) {
         super(traceId, code, message, cause);
     }
 }
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 4b130b40d9..3cfdd08f2c 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
@@ -17,8 +17,9 @@
 
 package org.apache.ignite.internal.schemasync;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.ignite.internal.SessionUtils.executeUpdate;
-import static org.apache.ignite.internal.TestWrappers.unwrapTableViewInternal;
+import static 
org.apache.ignite.internal.testframework.asserts.CompletableFutureAssert.assertWillThrow;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.is;
@@ -33,9 +34,10 @@ import org.apache.ignite.internal.tx.TxState;
 import org.apache.ignite.lang.ErrorGroups.Transactions;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.table.Tuple;
+import org.apache.ignite.tx.IncompatibleSchemaException;
 import org.apache.ignite.tx.Transaction;
-import org.apache.ignite.tx.TransactionException;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EnumSource;
 
@@ -62,7 +64,7 @@ class ItSchemaForwardCompatibilityTest extends 
ClusterPerTestIntegrationTest {
     }
 
     /**
-     * Makes sure forward-compatible schema changes happenning between 
transaction operations and
+     * Makes sure forward-compatible schema changes happening between 
transaction operations and
      * commit do not prevent a commit from happening.
      */
     @ParameterizedTest
@@ -79,18 +81,17 @@ class ItSchemaForwardCompatibilityTest extends 
ClusterPerTestIntegrationTest {
         assertDoesNotThrow(tx::commit);
     }
 
-    @SuppressWarnings("resource")
     private void writeIn(Transaction tx) {
         putInTx(cluster.node(0).tables().table(TABLE_NAME), tx);
     }
 
     /**
-     * Makes sure forward-incompatible schema changes happenning between 
transaction operations and
+     * Makes sure forward-incompatible schema changes happening between 
transaction operations and
      * commit prevent a commit from happening: instead, the transaction is 
aborted.
      */
     @ParameterizedTest
     @EnumSource(ForwardIncompatibleDdl.class)
-    void 
forwardIncompatibleSchemaChangesDoNotAllowCommitting(ForwardIncompatibleDdl 
ddl) {
+    void 
forwardIncompatibleSchemaChangesDoNotAllowSyncCommit(ForwardIncompatibleDdl 
ddl) {
         createTable();
 
         Table table = node.tables().table(TABLE_NAME);
@@ -101,9 +102,7 @@ class ItSchemaForwardCompatibilityTest extends 
ClusterPerTestIntegrationTest {
 
         ddl.executeOn(cluster);
 
-        int tableId = unwrapTableViewInternal(table).tableId();
-
-        TransactionException ex = assertThrows(TransactionException.class, 
tx::commit);
+        IncompatibleSchemaException ex = 
assertThrows(IncompatibleSchemaException.class, tx::commit);
         assertThat(
                 ex.getMessage(),
                 containsString(String.format(
@@ -114,8 +113,35 @@ class ItSchemaForwardCompatibilityTest extends 
ClusterPerTestIntegrationTest {
                 ))
         );
 
-        // TODO: IGNITE-20415 - assert that the failure is because of a 
changed schema.
-        assertThat(ex.code(), is(Transactions.TX_COMMIT_ERR));
+        assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
+
+        assertThat(tx.state(), is(TxState.ABORTED));
+    }
+
+    /**
+     * Makes sure forward-incompatible schema changes happening between 
transaction operations and
+     * commit prevent a commit from happening (for async API): instead, the 
transaction is aborted.
+     */
+    @Test
+    void forwardIncompatibleSchemaChangesDoNotAllowAsyncCommitting() {
+        createTable();
+
+        InternalTransaction tx = (InternalTransaction) 
node.transactions().begin();
+
+        writeIn(tx);
+
+        ForwardIncompatibleDdl.CHANGE_DEFAULT.executeOn(cluster);
+
+        IncompatibleSchemaException ex = assertWillThrow(tx.commitAsync(), 
IncompatibleSchemaException.class, 10, SECONDS);
+        assertThat(
+                ex.getMessage(),
+                containsString(
+                        "Commit failed because schema is not 
forward-compatible [fromSchemaVersion=1, toSchemaVersion=2, table=TEST, "
+                                + "details=Column default value changed]"
+                )
+        );
+
+        assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
 
         assertThat(tx.state(), is(TxState.ABORTED));
     }
@@ -136,7 +162,7 @@ class ItSchemaForwardCompatibilityTest extends 
ClusterPerTestIntegrationTest {
     private enum ForwardCompatibleDdl {
         ADD_NULLABLE_COLUMN("ALTER TABLE " + TABLE_NAME + " ADD COLUMN new_col 
INT"),
         ADD_COLUMN_WITH_DEFAULT("ALTER TABLE " + TABLE_NAME + " ADD COLUMN 
new_col INT NOT NULL DEFAULT 42"),
-        // TODO: IGNITE-19485, IGNITE-20315 - Uncomment this after column 
rename support gets aded.
+        // TODO: IGNITE-19485, IGNITE-20315 - Uncomment this after column 
rename support gets added.
         // RENAME_COLUMN("ALTER TABLE " + TABLE_NAME + " RENAME COLUMN 
not_null_int to new_col"),
         DROP_NOT_NULL("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN 
not_null_int DROP NOT NULL"),
         WIDEN_COLUMN_TYPE("ALTER TABLE " + TABLE_NAME + " ALTER COLUMN 
not_null_int SET DATA TYPE BIGINT");
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 093693fe06..90e4b701ea 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
@@ -30,7 +30,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.apache.ignite.internal.Cluster;
 import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
 import org.apache.ignite.internal.app.IgniteImpl;
-import 
org.apache.ignite.internal.table.distributed.replicator.IncompatibleSchemaException;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.tx.TxState;
 import org.apache.ignite.internal.util.ExceptionUtils;
@@ -40,8 +39,8 @@ import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.table.KeyValueView;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.table.Tuple;
+import org.apache.ignite.tx.IncompatibleSchemaException;
 import org.apache.ignite.tx.Transaction;
-import org.apache.ignite.tx.TransactionException;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.function.Executable;
@@ -100,13 +99,10 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
 
         alterTable(TABLE_NAME);
 
-        int errorCode;
-
-        int tableId = unwrapTableViewInternal(table).tableId();
+        IgniteException ex;
 
         if (operation.sql()) {
-            IgniteException ex = assertThrows(IgniteException.class, () -> 
operation.execute(table, tx, cluster));
-            errorCode = ex.code();
+            ex = assertThrows(IgniteException.class, () -> 
operation.execute(table, tx, cluster));
 
             assertThat(
                     ex.getMessage(),
@@ -116,8 +112,7 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
                     ))
             );
         } else {
-            IncompatibleSchemaException ex = 
assertThrows(IncompatibleSchemaException.class, () -> operation.execute(table, 
tx, cluster));
-            errorCode = ex.code();
+            ex = assertThrows(IncompatibleSchemaException.class, () -> 
operation.execute(table, tx, cluster));
 
             assertThat(
                     ex.getMessage(),
@@ -128,7 +123,7 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
             );
         }
 
-        assertThat(errorCode, is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
+        assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
 
         assertThat(tx.state(), is(TxState.ABORTED));
     }
@@ -296,19 +291,16 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
 
         dropTable(TABLE_NAME);
 
-        int tableId = unwrapTableViewInternal(table).tableId();
-
         Throwable ex = assertThrows(Throwable.class, () -> 
operation.executeOn(tx));
         ex = ExceptionUtils.unwrapCause(ex);
 
-        assertThat(ex, is(instanceOf(TransactionException.class)));
+        assertThat(ex, is(instanceOf(IncompatibleSchemaException.class)));
         assertThat(
                 ex.getMessage(),
                 containsString(String.format("Commit failed because a table 
was already dropped [table=%s]", table.name()))
         );
 
-        // TODO: IGNITE-20415 - assert that the failure is because of a 
changed schema.
-        assertThat(((TransactionException) ex).code(), 
is(Transactions.TX_COMMIT_ERR));
+        assertThat(((IncompatibleSchemaException) ex).code(), 
is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
 
         assertThat(tx.state(), is(TxState.ABORTED));
     }
@@ -356,8 +348,6 @@ class ItSchemaSyncSingleNodeTest extends 
ClusterPerTestIntegrationTest {
         Tuple keyTuple = Tuple.create().set("id", KEY);
         kvView.put(null, keyTuple, Tuple.create().set("val", "put-in-tx2"));
 
-        int tableId = unwrapTableViewInternal(table).tableId();
-
         Executable task = scan ? () -> consumeCursor(kvView.query(tx1, null)) 
: () -> kvView.get(tx1, keyTuple);
 
         IgniteException ex = assertThrows(IgniteException.class, task);
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
index 227b28e14d..929e46a5fb 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/table/ItDurableFinishTest.java
@@ -51,7 +51,6 @@ import org.apache.ignite.internal.replicator.TablePartitionId;
 import 
org.apache.ignite.internal.replicator.configuration.ReplicationConfiguration;
 import org.apache.ignite.internal.testframework.IgniteTestUtils;
 import org.apache.ignite.internal.tx.InternalTransaction;
-import org.apache.ignite.internal.tx.MismatchingTransactionOutcomeException;
 import org.apache.ignite.internal.tx.TxMeta;
 import org.apache.ignite.internal.tx.TxStateMeta;
 import org.apache.ignite.internal.tx.message.CleanupReplicatedInfoMessage;
@@ -60,9 +59,9 @@ import 
org.apache.ignite.internal.tx.message.TxCleanupMessageErrorResponse;
 import org.apache.ignite.internal.tx.message.TxCleanupMessageResponse;
 import org.apache.ignite.internal.tx.message.TxFinishReplicaRequest;
 import org.apache.ignite.internal.tx.storage.state.TxStateStorage;
-import org.apache.ignite.internal.util.ExceptionUtils;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.table.Tuple;
+import org.apache.ignite.tx.MismatchingTransactionOutcomeException;
 import org.apache.ignite.tx.TransactionException;
 import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.Test;
@@ -308,9 +307,7 @@ public class ItDurableFinishTest extends 
ClusterPerTestIntegrationTest {
         // Tx.commit should throw MismatchingTransactionOutcomeException.
         TransactionException transactionException = 
assertThrows(TransactionException.class, context.tx::commit);
 
-        Throwable cause = 
ExceptionUtils.unwrapCause(transactionException.getCause());
-
-        assertInstanceOf(MismatchingTransactionOutcomeException.class, 
cause.getCause());
+        assertInstanceOf(MismatchingTransactionOutcomeException.class, 
transactionException);
     }
 
     private void markTxAbortedInTxStateStorage(IgniteImpl primaryNode, 
InternalTransaction tx, Table publicTable) {
diff --git 
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
 
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
index 76cad253f5..8d43943ccc 100644
--- 
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
+++ 
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/ItTransactionRecoveryTest.java
@@ -74,7 +74,7 @@ import 
org.apache.ignite.internal.testframework.WithSystemProperty;
 import org.apache.ignite.internal.testframework.flow.TestFlowUtils;
 import org.apache.ignite.internal.tx.HybridTimestampTracker;
 import org.apache.ignite.internal.tx.InternalTransaction;
-import org.apache.ignite.internal.tx.MismatchingTransactionOutcomeException;
+import 
org.apache.ignite.internal.tx.MismatchingTransactionOutcomeInternalException;
 import org.apache.ignite.internal.tx.TxMeta;
 import org.apache.ignite.internal.tx.TxState;
 import org.apache.ignite.internal.tx.TxStateMeta;
@@ -531,7 +531,7 @@ public class ItTransactionRecoveryTest extends 
ClusterPerTestIntegrationTest {
 
         ErrorTimestampAwareReplicaResponse errorResponse = 
(ErrorTimestampAwareReplicaResponse) response;
 
-        assertInstanceOf(MismatchingTransactionOutcomeException.class, 
ExceptionUtils.unwrapCause(errorResponse.throwable()));
+        assertInstanceOf(MismatchingTransactionOutcomeInternalException.class, 
ExceptionUtils.unwrapCause(errorResponse.throwable()));
 
         assertEquals(TxState.ABORTED, txStoredState(commitPartNode, 
orphanTx.id()));
     }
@@ -647,7 +647,7 @@ public class ItTransactionRecoveryTest extends 
ClusterPerTestIntegrationTest {
                 rwTx1Id
         );
 
-        assertThat(finish2, 
willThrow(MismatchingTransactionOutcomeException.class));
+        assertThat(finish2, 
willThrow(MismatchingTransactionOutcomeInternalException.class));
     }
 
     @Test
@@ -792,7 +792,7 @@ public class ItTransactionRecoveryTest extends 
ClusterPerTestIntegrationTest {
         cancelLease(commitPartNode, tblReplicationGrp);
 
         TransactionException txEx = assertWillThrow(commitFut, 
TransactionException.class, 30, SECONDS);
-        assertThat(txEx.getCause(), 
instanceOf(MismatchingTransactionOutcomeException.class));
+        assertThat(txEx.getCause(), 
instanceOf(MismatchingTransactionOutcomeInternalException.class));
 
         RecordView<Tuple> view = 
txCrdNode.tables().table(TABLE_NAME).recordView();
 
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractTableView.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractTableView.java
index 70ff46fb76..2778dbf84b 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractTableView.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/AbstractTableView.java
@@ -40,7 +40,7 @@ import 
org.apache.ignite.internal.table.criteria.CursorAdapter;
 import org.apache.ignite.internal.table.criteria.QueryCriteriaAsyncCursor;
 import org.apache.ignite.internal.table.criteria.SqlSerializer;
 import org.apache.ignite.internal.table.criteria.SqlSerializer.Builder;
-import 
org.apache.ignite.internal.table.distributed.replicator.IncompatibleSchemaException;
+import 
org.apache.ignite.internal.table.distributed.replicator.IncompatibleSchemaVersionException;
 import 
org.apache.ignite.internal.table.distributed.replicator.InternalSchemaVersionMismatchException;
 import org.apache.ignite.internal.table.distributed.schema.SchemaVersions;
 import org.apache.ignite.internal.tx.InternalTransaction;
@@ -157,7 +157,7 @@ abstract class AbstractTableView<R> implements 
CriteriaQuerySource<R> {
 
                                 // Repeat.
                                 return withSchemaSync(tx, schemaVersion, 
action);
-                            } else if (tx == null && 
isOrCausedBy(IncompatibleSchemaException.class, ex)) {
+                            } else if (tx == null && 
isOrCausedBy(IncompatibleSchemaVersionException.class, ex)) {
                                 // Table version was changed while we were 
executing an implicit transaction (between it had been created
                                 // and the moment when the operation actually 
touched the partition), let's retry.
                                 
assertSchemaVersionIncreased(previousSchemaVersion, schemaVersion);
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionExceptionMapperProvider.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/TableExceptionMapperProvider.java
similarity index 69%
copy from 
modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionExceptionMapperProvider.java
copy to 
modules/table/src/main/java/org/apache/ignite/internal/table/TableExceptionMapperProvider.java
index 7c0dc5c40c..f558d1d812 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionExceptionMapperProvider.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/TableExceptionMapperProvider.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.tx;
+package org.apache.ignite.internal.table;
 
 import static org.apache.ignite.internal.lang.IgniteExceptionMapper.unchecked;
 
@@ -25,21 +25,22 @@ import java.util.Collection;
 import java.util.List;
 import org.apache.ignite.internal.lang.IgniteExceptionMapper;
 import org.apache.ignite.internal.lang.IgniteExceptionMappersProvider;
-import org.apache.ignite.internal.replicator.exception.ReplicationException;
-import org.apache.ignite.tx.TransactionException;
+import 
org.apache.ignite.internal.table.distributed.replicator.IncompatibleSchemaVersionException;
+import org.apache.ignite.tx.IncompatibleSchemaException;
 
 /**
- * Transaction module exception mapper.
+ * Table module exception mapper.
  */
 @AutoService(IgniteExceptionMappersProvider.class)
-public class TransactionExceptionMapperProvider implements 
IgniteExceptionMappersProvider {
+public class TableExceptionMapperProvider implements 
IgniteExceptionMappersProvider {
     @Override
     public Collection<IgniteExceptionMapper<?, ?>> mappers() {
         List<IgniteExceptionMapper<?, ?>> mappers = new ArrayList<>();
 
-        mappers.add(unchecked(LockException.class, err -> new 
TransactionException(err.traceId(), err.code(), err.getMessage(), err)));
-        mappers.add(unchecked(ReplicationException.class,
-                err -> new TransactionException(err.traceId(), err.code(), 
err.getMessage(), err)));
+        mappers.add(unchecked(
+                IncompatibleSchemaVersionException.class,
+                err -> new IncompatibleSchemaException(err.traceId(), 
err.code(), err.getMessage(), err)
+        ));
 
         return mappers;
     }
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/IncompatibleSchemaVersionException.java
similarity index 76%
rename from 
modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaException.java
rename to 
modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/IncompatibleSchemaVersionException.java
index 99f20b5fda..a184b81820 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/IncompatibleSchemaVersionException.java
@@ -18,15 +18,14 @@
 package org.apache.ignite.internal.table.distributed.replicator;
 
 import 
org.apache.ignite.internal.replicator.exception.ExpectedReplicationException;
+import org.apache.ignite.internal.tx.TransactionInternalException;
 import org.apache.ignite.lang.ErrorGroups.Transactions;
-import org.apache.ignite.tx.TransactionException;
 
 /**
  * Thrown when, during an attempt to execute a transactional operation, it 
turns out that the operation cannot be executed
  * because an incompatible schema change has happened.
  */
-// TODO: IGNITE-20415 - make this extend TransactionInternalException.
-public class IncompatibleSchemaException extends TransactionException 
implements ExpectedReplicationException {
+public class IncompatibleSchemaVersionException extends 
TransactionInternalException implements ExpectedReplicationException {
     private static final String SCHEMA_CHANGED_MESSAGE = "Table schema was 
updated after the transaction was started "
             + "[table=%s, startSchema=%d, operationSchema=%d]";
 
@@ -34,7 +33,7 @@ public class IncompatibleSchemaException extends 
TransactionException implements
 
     private static final String TABLE_DROPPED_ID_MESSAGE = "Table was dropped 
[tableId=%d]";
 
-    public IncompatibleSchemaException(String message) {
+    public IncompatibleSchemaVersionException(String message) {
         super(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR, message);
     }
 
@@ -46,8 +45,8 @@ public class IncompatibleSchemaException extends 
TransactionException implements
      * @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(
+    public static IncompatibleSchemaVersionException schemaChanged(String 
tableName, int startSchemaVersion, int operationSchemaVersion) {
+        return new IncompatibleSchemaVersionException(String.format(
                 SCHEMA_CHANGED_MESSAGE,
                 tableName, startSchemaVersion, operationSchemaVersion
         ));
@@ -59,8 +58,8 @@ public class IncompatibleSchemaException extends 
TransactionException implements
      * @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));
+    public static IncompatibleSchemaVersionException tableDropped(String 
tableName) {
+        return new 
IncompatibleSchemaVersionException(String.format(TABLE_DROPPED_NAME_MESSAGE, 
tableName));
     }
 
     /**
@@ -70,7 +69,7 @@ public class IncompatibleSchemaException extends 
TransactionException implements
      * @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));
+    public static IncompatibleSchemaVersionException tableDropped(int tableId) 
{
+        return new 
IncompatibleSchemaVersionException(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 3e5c67b2f3..3cb1abfbfc 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
@@ -174,11 +174,12 @@ import 
org.apache.ignite.internal.table.distributed.raft.UnexpectedTransactionSt
 import org.apache.ignite.internal.table.distributed.schema.SchemaSyncService;
 import 
org.apache.ignite.internal.table.distributed.schema.ValidationSchemasSource;
 import org.apache.ignite.internal.tx.HybridTimestampTracker;
+import org.apache.ignite.internal.tx.IncompatibleSchemaAbortException;
 import org.apache.ignite.internal.tx.Lock;
 import org.apache.ignite.internal.tx.LockKey;
 import org.apache.ignite.internal.tx.LockManager;
 import org.apache.ignite.internal.tx.LockMode;
-import org.apache.ignite.internal.tx.MismatchingTransactionOutcomeException;
+import 
org.apache.ignite.internal.tx.MismatchingTransactionOutcomeInternalException;
 import org.apache.ignite.internal.tx.TransactionMeta;
 import org.apache.ignite.internal.tx.TransactionResult;
 import org.apache.ignite.internal.tx.TxManager;
@@ -1065,7 +1066,7 @@ public class PartitionReplicaListener implements 
ReplicaListener {
         return schemaCompatValidator.validateBackwards(row.schemaVersion(), 
tableId(), txId)
                 .thenAccept(validationResult -> {
                     if (!validationResult.isSuccessful()) {
-                        throw new IncompatibleSchemaException(String.format(
+                        throw new 
IncompatibleSchemaVersionException(String.format(
                                 "Operation failed because it tried to access a 
row with newer schema version than transaction's [table=%s, "
                                         + "txSchemaVersion=%d, 
rowSchemaVersion=%d]",
                                 validationResult.failedTableName(), 
validationResult.fromSchemaVersion(), validationResult.toSchemaVersion()
@@ -1660,12 +1661,12 @@ public class PartitionReplicaListener implements 
ReplicaListener {
     private static void 
throwIfSchemaValidationOnCommitFailed(CompatValidationResult validationResult, 
TransactionResult txResult) {
         if (!validationResult.isSuccessful()) {
             if (validationResult.isTableDropped()) {
-                throw new MismatchingTransactionOutcomeException(
+                throw new IncompatibleSchemaAbortException(
                         format("Commit failed because a table was already 
dropped [table={}]", validationResult.failedTableName()),
                         txResult
                 );
             } else {
-                throw new MismatchingTransactionOutcomeException(
+                throw new IncompatibleSchemaAbortException(
                         format(
                                 "Commit failed because schema is not 
forward-compatible "
                                         + "[fromSchemaVersion={}, 
toSchemaVersion={}, table={}, details={}]",
@@ -1723,7 +1724,7 @@ public class PartitionReplicaListener implements 
ReplicaListener {
                         txMeta.txState()
                 );
 
-                throw new MismatchingTransactionOutcomeException(
+                throw new MismatchingTransactionOutcomeInternalException(
                         "Failed to change the outcome of a finished 
transaction [txId=" + txId + ", txState=" + txMeta.txState() + "].",
                         new TransactionResult(txMeta.txState(), 
txMeta.commitTimestamp())
                 );
@@ -1777,7 +1778,7 @@ public class PartitionReplicaListener implements 
ReplicaListener {
 
                             markFinished(txId, result.transactionState(), 
result.commitTimestamp());
 
-                            throw new 
MismatchingTransactionOutcomeException(utse.getMessage(), 
utse.transactionResult());
+                            throw new 
MismatchingTransactionOutcomeInternalException(utse.getMessage(), 
utse.transactionResult());
                         }
                         // Otherwise we convert from the internal exception to 
the client one.
                         throw new TransactionException(commit ? TX_COMMIT_ERR 
: TX_ROLLBACK_ERR, ex);
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 303a2b8d2a..1e31b33c86 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
@@ -246,11 +246,11 @@ class SchemaCompatibilityValidator {
         assert tableAtBeginTs != null;
 
         if (tableAtOpTs == null) {
-            throw 
IncompatibleSchemaException.tableDropped(tableAtBeginTs.name());
+            throw 
IncompatibleSchemaVersionException.tableDropped(tableAtBeginTs.name());
         }
 
         if (tableAtOpTs.tableVersion() != tableAtBeginTs.tableVersion()) {
-            throw IncompatibleSchemaException.schemaChanged(
+            throw IncompatibleSchemaVersionException.schemaChanged(
                     tableAtBeginTs.name(),
                     tableAtBeginTs.tableVersion(),
                     tableAtOpTs.tableVersion()
@@ -262,7 +262,7 @@ class SchemaCompatibilityValidator {
         CatalogTableDescriptor tableAtOpTs = catalogService.table(tableId, 
operationTimestamp.longValue());
 
         if (tableAtOpTs == null) {
-            throw IncompatibleSchemaException.tableDropped(tableId);
+            throw IncompatibleSchemaVersionException.tableDropped(tableId);
         }
     }
 
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
index 1dab412af1..76983328e0 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
+++ 
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
@@ -24,7 +24,7 @@ import java.util.UUID;
 import org.apache.ignite.internal.tx.TransactionInternalException;
 
 /** Error that occurs when a stale operation of a completed transaction is 
detected. */
-// TODO: IGNITE-20415 - make this exception public.
+// TODO: IGNITE-22748 - make this exception public?
 public class StaleTransactionOperationException extends 
TransactionInternalException {
     /**
      * Constructor.
diff --git 
a/modules/table/src/test/java/org/apache/ignite/internal/table/TableExceptionMapperProviderTest.java
 
b/modules/table/src/test/java/org/apache/ignite/internal/table/TableExceptionMapperProviderTest.java
new file mode 100644
index 0000000000..5dd462995b
--- /dev/null
+++ 
b/modules/table/src/test/java/org/apache/ignite/internal/table/TableExceptionMapperProviderTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.table;
+
+import static java.util.stream.Collectors.toList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.ignite.internal.lang.IgniteExceptionMapper;
+import 
org.apache.ignite.internal.table.distributed.replicator.IncompatibleSchemaVersionException;
+import org.apache.ignite.tx.IncompatibleSchemaException;
+import org.junit.jupiter.api.Test;
+
+class TableExceptionMapperProviderTest {
+    private final TableExceptionMapperProvider provider = new 
TableExceptionMapperProvider();
+
+    @Test
+    void translatesIncompatibleSchemaVersionException() {
+        Collection<IgniteExceptionMapper<?, ?>> mappers = provider.mappers();
+
+        List<IgniteExceptionMapper<?, ?>> matchingMappers = mappers.stream()
+                .filter(mapper -> 
IncompatibleSchemaVersionException.class.equals(mapper.mappingFrom()))
+                .collect(toList());
+
+        assertThat(matchingMappers, hasSize(1));
+
+        var mapper = 
(IgniteExceptionMapper<IncompatibleSchemaVersionException, 
IncompatibleSchemaException>) matchingMappers.get(0);
+
+        IncompatibleSchemaVersionException originalException = new 
IncompatibleSchemaVersionException("Oops");
+        IncompatibleSchemaException translationResult = 
mapper.map(originalException);
+
+        assertThat(translationResult.traceId(), 
is(originalException.traceId()));
+        assertThat(translationResult.code(), is(originalException.code()));
+        assertThat(translationResult.getMessage(), 
is(originalException.getMessage()));
+    }
+}
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 2d1cd8018c..89ea604cd5 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
@@ -191,7 +191,7 @@ import 
org.apache.ignite.internal.table.distributed.index.IndexUpdateHandler;
 import org.apache.ignite.internal.table.distributed.index.MetaIndexStatus;
 import 
org.apache.ignite.internal.table.distributed.index.MetaIndexStatusChange;
 import org.apache.ignite.internal.table.distributed.raft.PartitionDataStorage;
-import 
org.apache.ignite.internal.table.distributed.replicator.IncompatibleSchemaException;
+import 
org.apache.ignite.internal.table.distributed.replicator.IncompatibleSchemaVersionException;
 import 
org.apache.ignite.internal.table.distributed.replicator.InternalSchemaVersionMismatchException;
 import 
org.apache.ignite.internal.table.distributed.replicator.PartitionReplicaListener;
 import 
org.apache.ignite.internal.table.distributed.replicator.StaleTransactionOperationException;
@@ -205,8 +205,8 @@ import 
org.apache.ignite.internal.table.impl.DummySchemaManagerImpl;
 import org.apache.ignite.internal.testframework.IgniteAbstractTest;
 import org.apache.ignite.internal.tostring.IgniteToStringInclude;
 import org.apache.ignite.internal.tostring.S;
+import org.apache.ignite.internal.tx.IncompatibleSchemaAbortException;
 import org.apache.ignite.internal.tx.LockManager;
-import org.apache.ignite.internal.tx.MismatchingTransactionOutcomeException;
 import org.apache.ignite.internal.tx.TransactionMeta;
 import org.apache.ignite.internal.tx.TransactionResult;
 import org.apache.ignite.internal.tx.TxManager;
@@ -1809,8 +1809,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
 
         CompletableFuture<?> future = beginAndCommitTx();
 
-        MismatchingTransactionOutcomeException ex = assertWillThrowFast(future,
-                MismatchingTransactionOutcomeException.class);
+        IncompatibleSchemaAbortException ex = assertWillThrowFast(future, 
IncompatibleSchemaAbortException.class);
 
         assertThat(ex.getMessage(), containsString("Commit failed because 
schema is not forward-compatible [fromSchemaVersion=1, "
                 + "toSchemaVersion=2, table=test, details=Column default value 
changed]"));
@@ -1902,8 +1901,8 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
             CompletableFuture<?> future,
             AtomicReference<Boolean> committed
     ) {
-        IncompatibleSchemaException ex = assertWillThrowFast(future,
-                IncompatibleSchemaException.class);
+        IncompatibleSchemaVersionException ex = assertWillThrowFast(future,
+                IncompatibleSchemaVersionException.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=test, "
@@ -2240,7 +2239,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
         }
 
         if (expectValidationFailure) {
-            IncompatibleSchemaException ex = assertWillThrowFast(future, 
IncompatibleSchemaException.class);
+            IncompatibleSchemaVersionException ex = 
assertWillThrowFast(future, IncompatibleSchemaVersionException.class);
             assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
             assertThat(
                     ex.getMessage(),
@@ -2360,7 +2359,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
 
         CompletableFuture<?> future = listenerInvocation.invoke(txId, key);
 
-        IncompatibleSchemaException ex = assertWillThrowFast(future, 
IncompatibleSchemaException.class);
+        IncompatibleSchemaVersionException ex = assertWillThrowFast(future, 
IncompatibleSchemaVersionException.class);
         assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
         assertThat(ex.getMessage(), is("Table was dropped [tableId=1]"));
     }
@@ -2458,7 +2457,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
 
         CompletableFuture<?> future = listenerInvocation.invoke(txId, readTs, 
key);
 
-        IncompatibleSchemaException ex = assertWillThrowFast(future, 
IncompatibleSchemaException.class);
+        IncompatibleSchemaVersionException ex = assertWillThrowFast(future, 
IncompatibleSchemaVersionException.class);
         assertThat(ex.code(), is(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR));
         assertThat(ex.getMessage(), is("Table was dropped [tableId=1]"));
     }
@@ -2529,7 +2528,7 @@ public class PartitionReplicaListenerTest extends 
IgniteAbstractTest {
                 localNode.id()
         );
 
-        MismatchingTransactionOutcomeException ex = 
assertWillThrowFast(future, MismatchingTransactionOutcomeException.class);
+        IncompatibleSchemaAbortException ex = assertWillThrowFast(future, 
IncompatibleSchemaAbortException.class);
 
         assertThat(ex.getMessage(), is("Commit failed because a table was 
already dropped [table=" + tableNameToBeDropped + "]"));
 
diff --git 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/IncompatibleSchemaAbortException.java
similarity index 50%
copy from 
modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
copy to 
modules/transactions/src/main/java/org/apache/ignite/internal/tx/IncompatibleSchemaAbortException.java
index 1dab412af1..e63d0409c1 100644
--- 
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/replicator/StaleTransactionOperationException.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/IncompatibleSchemaAbortException.java
@@ -15,23 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.table.distributed.replicator;
+package org.apache.ignite.internal.tx;
 
-import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
-import static 
org.apache.ignite.lang.ErrorGroups.Transactions.TX_STALE_OPERATION_ERR;
+import org.apache.ignite.lang.ErrorGroups.Transactions;
 
-import java.util.UUID;
-import org.apache.ignite.internal.tx.TransactionInternalException;
-
-/** Error that occurs when a stale operation of a completed transaction is 
detected. */
-// TODO: IGNITE-20415 - make this exception public.
-public class StaleTransactionOperationException extends 
TransactionInternalException {
-    /**
-     * Constructor.
-     *
-     * @param txId Transaction ID.
-     */
-    public StaleTransactionOperationException(UUID txId) {
-        super(TX_STALE_OPERATION_ERR, format("Stale operation of a completed 
transaction was detected: [txId={}]", txId));
+/**
+ * Thrown when a commit attempt fails due to incompatible schema change (that 
is, the transaction was started on some schema
+ * V1, but at the commit timestamp another schema V2 is effective, and it's 
not forward-compatible with V1).
+ */
+public class IncompatibleSchemaAbortException extends 
MismatchingTransactionOutcomeInternalException {
+    public IncompatibleSchemaAbortException(String message, TransactionResult 
transactionResult) {
+        super(Transactions.TX_INCOMPATIBLE_SCHEMA_ERR, message, 
transactionResult, null);
     }
 }
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeInternalException.java
similarity index 79%
rename from 
modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
rename to 
modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeInternalException.java
index 6fc15f87c7..6fd3c73a39 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeException.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/MismatchingTransactionOutcomeInternalException.java
@@ -19,14 +19,15 @@ package org.apache.ignite.internal.tx;
 
 import static 
org.apache.ignite.lang.ErrorGroups.Transactions.TX_UNEXPECTED_STATE_ERR;
 
+import org.jetbrains.annotations.Nullable;
+
 /**
  * The exception is thrown when the transaction result differs from the 
intended one.
  *
  * <p>For example, {@code tx.commit()} is called for a transaction, but the 
verification logic decided to abort it instead. The transaction
  * will be finished with {@link TxState#ABORTED} and the call to {@code 
tx.commit()} will throw this exception.
  */
-// TODO: IGNITE-20415 - split this into public exception (in a public package) 
and internal exception (carrying internal state).
-public class MismatchingTransactionOutcomeException extends 
TransactionInternalException {
+public class MismatchingTransactionOutcomeInternalException extends 
TransactionInternalException {
 
     private static final long serialVersionUID = -7953057695915339651L;
 
@@ -36,13 +37,18 @@ public class MismatchingTransactionOutcomeException extends 
TransactionInternalE
     /**
      * Constructor.
      */
-    public MismatchingTransactionOutcomeException(int errorCode, String 
message, TransactionResult transactionResult, Throwable cause) {
+    public MismatchingTransactionOutcomeInternalException(
+            int errorCode,
+            String message,
+            TransactionResult transactionResult,
+            @Nullable Throwable cause
+    ) {
         super(errorCode, message, cause);
 
         this.transactionResult = transactionResult;
     }
 
-    public MismatchingTransactionOutcomeException(String message, 
TransactionResult transactionResult) {
+    public MismatchingTransactionOutcomeInternalException(String message, 
TransactionResult transactionResult) {
         this(TX_UNEXPECTED_STATE_ERR, message, transactionResult, null);
     }
 
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionExceptionMapperProvider.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionExceptionMapperProvider.java
index 7c0dc5c40c..35af89caa0 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionExceptionMapperProvider.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/TransactionExceptionMapperProvider.java
@@ -26,6 +26,8 @@ import java.util.List;
 import org.apache.ignite.internal.lang.IgniteExceptionMapper;
 import org.apache.ignite.internal.lang.IgniteExceptionMappersProvider;
 import org.apache.ignite.internal.replicator.exception.ReplicationException;
+import org.apache.ignite.tx.IncompatibleSchemaException;
+import org.apache.ignite.tx.MismatchingTransactionOutcomeException;
 import org.apache.ignite.tx.TransactionException;
 
 /**
@@ -40,6 +42,10 @@ public class TransactionExceptionMapperProvider implements 
IgniteExceptionMapper
         mappers.add(unchecked(LockException.class, err -> new 
TransactionException(err.traceId(), err.code(), err.getMessage(), err)));
         mappers.add(unchecked(ReplicationException.class,
                 err -> new TransactionException(err.traceId(), err.code(), 
err.getMessage(), err)));
+        
mappers.add(unchecked(MismatchingTransactionOutcomeInternalException.class,
+                err -> new 
MismatchingTransactionOutcomeException(err.traceId(), err.code(), 
err.getMessage(), err)));
+        mappers.add(unchecked(IncompatibleSchemaAbortException.class,
+                err -> new IncompatibleSchemaException(err.traceId(), 
err.code(), err.getMessage(), err)));
 
         return mappers;
     }
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionInflights.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionInflights.java
index 58fa141a32..cd6f09c75e 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionInflights.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TransactionInflights.java
@@ -38,7 +38,7 @@ import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.lang.IgniteBiTuple;
 import org.apache.ignite.internal.placementdriver.PlacementDriver;
 import org.apache.ignite.internal.replicator.TablePartitionId;
-import org.apache.ignite.internal.tx.MismatchingTransactionOutcomeException;
+import 
org.apache.ignite.internal.tx.MismatchingTransactionOutcomeInternalException;
 import org.apache.ignite.internal.tx.TransactionResult;
 import org.apache.ignite.internal.tx.message.FinishedTransactionsBatchMessage;
 import org.apache.ignite.network.ClusterNode;
@@ -266,7 +266,7 @@ public class TransactionInflights {
                 Throwable unwrappedReadyToFinishException = 
unwrapCause(readyToFinishException);
 
                 if (commit && unwrappedReadyToFinishException instanceof 
PrimaryReplicaExpiredException) {
-                    finishInProgressFuture.completeExceptionally(new 
MismatchingTransactionOutcomeException(
+                    finishInProgressFuture.completeExceptionally(new 
MismatchingTransactionOutcomeInternalException(
                             TX_PRIMARY_REPLICA_EXPIRED_ERR,
                             "Failed to commit the transaction.",
                             new TransactionResult(ABORTED, null),
diff --git 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
index fa416eeb62..542952d0a6 100644
--- 
a/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
+++ 
b/modules/transactions/src/main/java/org/apache/ignite/internal/tx/impl/TxManagerImpl.java
@@ -94,7 +94,7 @@ import org.apache.ignite.internal.tx.HybridTimestampTracker;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.apache.ignite.internal.tx.LocalRwTxCounter;
 import org.apache.ignite.internal.tx.LockManager;
-import org.apache.ignite.internal.tx.MismatchingTransactionOutcomeException;
+import 
org.apache.ignite.internal.tx.MismatchingTransactionOutcomeInternalException;
 import org.apache.ignite.internal.tx.TransactionMeta;
 import org.apache.ignite.internal.tx.TransactionResult;
 import org.apache.ignite.internal.tx.TxManager;
@@ -566,7 +566,7 @@ public class TxManagerImpl implements TxManager, 
NetworkMessageHandler {
             return nullCompletedFuture();
         }
 
-        return failedFuture(new MismatchingTransactionOutcomeException(
+        return failedFuture(new MismatchingTransactionOutcomeInternalException(
                 "Failed to change the outcome of a finished transaction 
[txId=" + txId + ", txState=" + stateMeta.txState() + "].",
                 new TransactionResult(stateMeta.txState(), 
stateMeta.commitTimestamp()))
         );
@@ -640,8 +640,9 @@ public class TxManagerImpl implements TxManager, 
NetworkMessageHandler {
                     if (ex != null) {
                         Throwable cause = ExceptionUtils.unwrapCause(ex);
 
-                        if (cause instanceof 
MismatchingTransactionOutcomeException) {
-                            MismatchingTransactionOutcomeException 
transactionException = (MismatchingTransactionOutcomeException) cause;
+                        if (cause instanceof 
MismatchingTransactionOutcomeInternalException) {
+                            MismatchingTransactionOutcomeInternalException 
transactionException =
+                                    
(MismatchingTransactionOutcomeInternalException) cause;
 
                             TransactionResult result = 
transactionException.transactionResult();
 
@@ -740,7 +741,7 @@ public class TxManagerImpl implements TxManager, 
NetworkMessageHandler {
                     txResult.transactionState()
             );
 
-            throw new MismatchingTransactionOutcomeException(
+            throw new MismatchingTransactionOutcomeInternalException(
                     "Failed to change the outcome of a finished transaction 
[txId=" + txId + ", txState=" + txResult.transactionState()
                             + "].",
                     txResult
diff --git 
a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
 
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
index 11d25032e7..4a164e3eb9 100644
--- 
a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
+++ 
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/TxManagerTest.java
@@ -31,6 +31,7 @@ import static 
org.apache.ignite.lang.ErrorGroups.Transactions.TX_PRIMARY_REPLICA
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -76,7 +77,6 @@ import org.apache.ignite.internal.replicator.ReplicaService;
 import org.apache.ignite.internal.replicator.TablePartitionId;
 import 
org.apache.ignite.internal.replicator.exception.PrimaryReplicaMissException;
 import org.apache.ignite.internal.testframework.IgniteAbstractTest;
-import org.apache.ignite.internal.testframework.IgniteTestUtils;
 import org.apache.ignite.internal.tx.configuration.TransactionConfiguration;
 import org.apache.ignite.internal.tx.impl.HeapLockManager;
 import org.apache.ignite.internal.tx.impl.PrimaryReplicaExpiredException;
@@ -90,6 +90,7 @@ import org.apache.ignite.internal.tx.test.TestTransactionIds;
 import org.apache.ignite.lang.ErrorGroups.Transactions;
 import org.apache.ignite.network.ClusterNode;
 import org.apache.ignite.network.NetworkAddress;
+import org.apache.ignite.tx.MismatchingTransactionOutcomeException;
 import org.apache.ignite.tx.TransactionException;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.AfterEach;
@@ -327,7 +328,7 @@ public class TxManagerTest extends IgniteAbstractTest {
 
         when(replicaService.invoke(anyString(), 
any(TxFinishReplicaRequest.class)))
                 .thenReturn(failedFuture(
-                        new MismatchingTransactionOutcomeException(
+                        new MismatchingTransactionOutcomeInternalException(
                                 "Test exception",
                                 new TransactionResult(TxState.ABORTED, null
                                 )
@@ -342,7 +343,7 @@ public class TxManagerTest extends IgniteAbstractTest {
 
         TransactionException transactionException = 
assertThrows(TransactionException.class, tx::commit);
 
-        assertTrue(IgniteTestUtils.hasCause(transactionException, 
MismatchingTransactionOutcomeException.class, null));
+        assertInstanceOf(MismatchingTransactionOutcomeException.class, 
transactionException);
 
         tx.commitAsync().get(3, TimeUnit.SECONDS);
         tx.rollbackAsync().get(3, TimeUnit.SECONDS);
@@ -355,7 +356,7 @@ public class TxManagerTest extends IgniteAbstractTest {
 
         when(replicaService.invoke(anyString(), 
any(TxFinishReplicaRequest.class)))
                 .thenReturn(failedFuture(
-                        new MismatchingTransactionOutcomeException(
+                        new MismatchingTransactionOutcomeInternalException(
                                 "Test exception",
                                 new TransactionResult(TxState.ABORTED, null
                                 )
@@ -370,7 +371,7 @@ public class TxManagerTest extends IgniteAbstractTest {
 
         TransactionException transactionException = 
assertThrows(TransactionException.class, tx::rollback);
 
-        assertTrue(IgniteTestUtils.hasCause(transactionException, 
MismatchingTransactionOutcomeException.class, null));
+        assertInstanceOf(MismatchingTransactionOutcomeException.class, 
transactionException);
 
         tx.commitAsync().get(3, TimeUnit.SECONDS);
         tx.rollbackAsync().get(3, TimeUnit.SECONDS);
@@ -582,7 +583,7 @@ public class TxManagerTest extends IgniteAbstractTest {
         when(placementDriver.awaitPrimaryReplica(any(), any(), anyLong(), 
any())).thenReturn(completedFuture(
                 new TestReplicaMetaImpl(LOCAL_NODE, hybridTimestamp(1), 
hybridTimestamp(10))));
         when(replicaService.invoke(anyString(), 
any(TxFinishReplicaRequest.class)))
-                .thenReturn(failedFuture(new 
MismatchingTransactionOutcomeException(
+                .thenReturn(failedFuture(new 
MismatchingTransactionOutcomeInternalException(
                         "TX already finished.",
                         new TransactionResult(TxState.ABORTED, null)
                 )));

Reply via email to