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

korlov 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 5c2982065de IGNITE-27321 Thin Client. DML failed in explicit 
transaction (#7216)
5c2982065de is described below

commit 5c2982065decc7e4f08845b934393a92d0d87c4a
Author: korlov42 <[email protected]>
AuthorDate: Fri Dec 12 14:40:51 2025 +0200

    IGNITE-27321 Thin Client. DML failed in explicit transaction (#7216)
---
 .../ignite/internal/client/sql/ClientSql.java      | 26 ++++++++++--
 .../runner/app/client/ItThinClientSqlTest.java     | 49 ++++++++++++++++++++++
 2 files changed, 72 insertions(+), 3 deletions(-)

diff --git 
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
 
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
index a9a3afc0751..2360f0f4f95 100644
--- 
a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
+++ 
b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSql.java
@@ -334,9 +334,7 @@ public class ClientSql implements IgniteSql {
 
         PartitionMappingProvider mappingProvider = 
mappingProviderCache.getIfPresent(new PaCacheKey(statement));
 
-        PartitionMapping mapping = mappingProvider != null
-                ? mappingProvider.get(arguments)
-                : null;
+        PartitionMapping mapping = resolveMapping(transaction, 
mappingProvider, arguments);
 
         // Write context carries request execution details over async chain.
         WriteContext ctx = new WriteContext(ch.observableTimestamp(), 
ClientOp.SQL_EXEC);
@@ -371,6 +369,28 @@ public class ClientSql implements IgniteSql {
         )).exceptionally(ClientSql::handleException);
     }
 
+    private static @Nullable PartitionMapping resolveMapping(
+            @Nullable Transaction transaction,
+            @Nullable PartitionMappingProvider provider,
+            @Nullable Object... arguments
+    ) {
+        if (provider == null) {
+            // Nothing to resolve.
+            return null;
+        }
+
+        if (explicitRw(transaction) && provider.directTxMode() == 
ClientDirectTxMode.NOT_SUPPORTED) {
+            // Current statement doesn't support direct transactions which is 
part of the explicit RW transactions handling.
+            return null;
+        }
+
+        return provider.get(arguments);
+    }
+
+    private static boolean explicitRw(@Nullable Transaction transaction) {
+        return transaction != null && !transaction.isReadOnly();
+    }
+
     private <T> PayloadReader<AsyncResultSet<T>> payloadReader(
             WriteContext ctx,
             @Nullable Mapper<T> mapper,
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java
index ad1bd9371ab..e2db70b5314 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java
@@ -19,6 +19,9 @@ package org.apache.ignite.internal.runner.app.client;
 
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -62,10 +65,12 @@ import org.apache.ignite.sql.SqlRow;
 import org.apache.ignite.sql.Statement;
 import org.apache.ignite.sql.Statement.StatementBuilder;
 import org.apache.ignite.sql.async.AsyncResultSet;
+import org.apache.ignite.table.KeyValueView;
 import org.apache.ignite.table.Table;
 import org.apache.ignite.table.mapper.Mapper;
 import org.apache.ignite.tx.Transaction;
 import org.apache.ignite.tx.TransactionOptions;
+import org.awaitility.Awaitility;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Disabled;
@@ -861,6 +866,50 @@ public class ItThinClientSqlTest extends 
ItAbstractThinClientTest {
         }
     }
 
+    @Test
+    void testKvAndDistributedDmlInSameTransaction() {
+        IgniteSql sql = client().sql();
+        sql.executeScript("CREATE TABLE my_table (id INT PRIMARY KEY, val 
INT)");
+
+        KeyValueView<Integer, Integer> view = client().tables()
+                .table("my_table")
+                .keyValueView(Integer.class, Integer.class);
+
+        // First, we need to await table creation.
+        for (int i = 0; i < 10; i++) {
+            view.put(null, i, 0);
+        }
+
+        Transaction tx = client().transactions().begin();
+
+        try {
+            // Then run few operations to make sure transaction is initialized 
with commitPartition.
+            for (int i = 0; i < 10; i++) {
+                view.put(tx, i, 0);
+            }
+
+            // Run first batch of distributed DML queries. PartitionAwareness 
meta may be not prepared yet.
+            for (int i = 0; i < 5; i++) {
+                try (ResultSet<?> ignored = sql.execute(tx, "UPDATE my_table 
SET val = val * 10 WHERE id = ?", i)) {
+                    // NO-OP
+                }
+            }
+
+            // Hence, let's wait for PA meta to be ready.
+            Awaitility.await().until(() -> ((ClientSql) 
sql).partitionAwarenessCachedMetas(), is(not(empty())));
+
+            // And retry distributed DML queries one more time. Here, even 
though PA meta is available, we expect
+            // these statements to be executed in proxy mode and no exceptions 
should be thrown.
+            for (int i = 0; i < 5; i++) {
+                try (ResultSet<?> ignored = sql.execute(tx, "UPDATE my_table 
SET val = val * 10 WHERE id = ?", i)) {
+                    // NO-OP
+                }
+            }
+        } finally {
+            tx.rollback();
+        }
+    }
+
     private static class Pojo {
         public int num;
 

Reply via email to