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

timoninmaxim pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 17a14f28538 IGNITE-27666 Fix missed SessionContext in CacheInterceptor 
for jdbc calls (#12660)
17a14f28538 is described below

commit 17a14f28538a57e145f65ae6e16a7e565ef21b66
Author: Maksim Timonin <[email protected]>
AuthorDate: Tue Feb 17 14:43:07 2026 +0300

    IGNITE-27666 Fix missed SessionContext in CacheInterceptor for jdbc calls 
(#12660)
---
 .../query/calcite/exec/rel/ModifyNode.java         |   7 +
 .../JdbcSetClientInfoCacheInterceptorTest.java     | 204 +++++++++++++++++++++
 .../query/calcite/jdbc/JdbcSetClientInfoTest.java  |  41 ++++-
 .../apache/ignite/testsuites/JdbcTestSuite.java    |   2 +
 .../processors/cache/GridCacheProxyImpl.java       |  23 +++
 .../internal/processors/odbc/ClientTxSupport.java  |   7 +-
 .../processors/odbc/jdbc/JdbcRequestHandler.java   |   3 +-
 .../platform/client/tx/ClientTxStartRequest.java   |   3 +-
 8 files changed, 284 insertions(+), 6 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ModifyNode.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ModifyNode.java
index b84f5fe0b2e..650a775514d 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ModifyNode.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ModifyNode.java
@@ -29,6 +29,7 @@ import javax.cache.processor.MutableEntry;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.cache.context.SessionContextImpl;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.GridCacheProxyImpl;
 import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
@@ -218,6 +219,12 @@ public class ModifyNode<Row> extends AbstractNode<Row> 
implements SingleNode<Row
         List<ModifyTuple> tuples,
         GridCacheProxyImpl<Object, Object> cache
     ) throws IgniteCheckedException {
+        SessionContextImpl sesCtx = context().unwrap(SessionContextImpl.class);
+        Map<String, String> sesAttrs = sesCtx == null ? null : 
sesCtx.attributes();
+
+        if (sesAttrs != null)
+            cache = cache.withApplicationAttributes(sesAttrs);
+
         Map<Object, EntryProcessor<Object, Object, Long>> map = 
invokeMap(tuples);
         Map<Object, EntryProcessorResult<Long>> res = 
cacheForDML(cache).invokeAll(map);
 
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoCacheInterceptorTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoCacheInterceptorTest.java
new file mode 100644
index 00000000000..300b10679d8
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoCacheInterceptorTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.processors.query.calcite.jdbc;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.Collection;
+import java.util.List;
+import javax.cache.Cache;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheInterceptorAdapter;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.SqlConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.resources.SessionContextProviderResource;
+import org.apache.ignite.session.SessionContextProvider;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assume.assumeFalse;
+
+/** */
+@RunWith(Parameterized.class)
+public class JdbcSetClientInfoCacheInterceptorTest extends 
GridCommonAbstractTest {
+    /** */
+    private static final String SESSION_ID = "sessionId";
+
+    /** */
+    private static final String URL = "jdbc:ignite:thin://127.0.0.1";
+
+    /** */
+    @Parameterized.Parameter
+    public boolean runInTx;
+
+    /** */
+    @Parameterized.Parameter(1)
+    public CacheAtomicityMode cacheMode;
+
+    /** */
+    @Parameterized.Parameters(name = "runInTx={0}, mode={1}")
+    public static Collection<Object[]> data() {
+        return GridTestUtils.cartesianProduct(
+            F.asList(false, true),
+            F.asList(CacheAtomicityMode.TRANSACTIONAL, 
CacheAtomicityMode.ATOMIC));
+    }
+
+    /** */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.setSqlConfiguration(new SqlConfiguration()
+            .setQueryEnginesConfiguration(new 
CalciteQueryEngineConfiguration().setDefault(true)));
+
+        cfg.getTransactionConfiguration().setTxAwareQueriesEnabled(runInTx);
+
+        QueryEntity entity = new QueryEntity()
+            .setTableName("MYTABLE")
+            .setKeyType(Integer.class.getName())
+            .setValueType(String.class.getName())
+            .addQueryField("id", Integer.class.getName(), null)
+            .addQueryField("sessionId", String.class.getName(), null)
+            .setKeyFieldName("id")
+            .setValueFieldName("sessionId");
+
+        cfg.setCacheConfiguration(new CacheConfiguration<Integer, String>()
+            .setAtomicityMode(cacheMode)
+            .setName(DEFAULT_CACHE_NAME)
+            .setSqlSchema("PUBLIC")
+            .setQueryEntities(List.of(entity))
+            .setInterceptor(new SessionContextCacheInterceptor()));
+
+        return cfg;
+    }
+
+    /** */
+    @Override protected void beforeTest() {
+        assumeFalse(runInTx && cacheMode == CacheAtomicityMode.ATOMIC);
+    }
+
+    /** */
+    @Test
+    public void testInterceptInsert() throws Exception {
+        try (Ignite ignore = startGrid(); Connection conn = 
DriverManager.getConnection(URL)) {
+            String sessionid = "42";
+
+            conn.setClientInfo(SESSION_ID, sessionid);
+
+            try (Statement s = conn.createStatement()) {
+                assertEquals(1, s.executeUpdate(
+                    "insert into PUBLIC.MYTABLE(id, sessionId) values (0, 
'must be changed to sessionId in CacheInterceptor');"));
+            }
+
+            checkRecordExist(conn, sessionid);
+        }
+    }
+
+    /** */
+    @Test
+    public void testInterceptUpdate() throws Exception {
+        try (Ignite ignore = startGrid(); Connection conn = 
DriverManager.getConnection(URL)) {
+            try (Statement s = conn.createStatement()) {
+                assertEquals(1, s.executeUpdate("insert into 
PUBLIC.MYTABLE(id, sessionId) values (0, '1');"));
+            }
+
+            String sessionid = "42";
+
+            conn.setClientInfo(SESSION_ID, sessionid);
+
+            try (Statement s = conn.createStatement()) {
+                assertEquals(1, s.executeUpdate(
+                    "update PUBLIC.MYTABLE set sessionId = 'must be changed to 
sessionId in CacheInterceptor' where id = 0;"));
+            }
+
+            checkRecordExist(conn, sessionid);
+        }
+    }
+
+    /** */
+    @Test
+    public void testInterceptDelete() throws Exception {
+        try (Ignite ignore = startGrid(); Connection conn = 
DriverManager.getConnection(URL)) {
+            try (Statement s = conn.createStatement()) {
+                assertEquals(1, s.executeUpdate(
+                    "insert into PUBLIC.MYTABLE(id, sessionId) values (0, 
'survives deletion due to CacheInterceptor');"));
+            }
+
+            conn.setClientInfo(SESSION_ID, "42");
+
+            try (Statement s = conn.createStatement()) {
+                // Record will be kept in table because of 
SessionContextCacheInterceptor#onBeforeRemove cancels the remove.
+                s.executeUpdate("delete from PUBLIC.MYTABLE where id = 0;");
+            }
+
+            checkRecordExist(conn, "survives deletion due to 
CacheInterceptor");
+        }
+    }
+
+    /** */
+    private void checkRecordExist(Connection conn, String expVal) throws 
Exception {
+        try (Statement s = conn.createStatement()) {
+            assertTrue(s.execute("select id, sessionId from PUBLIC.MYTABLE;"));
+
+            ResultSet rs = s.getResultSet();
+            assertTrue(rs.next());
+
+            assertEquals(0, rs.getInt("id"));
+            assertEquals(expVal, rs.getString("sessionId"));
+        }
+    }
+
+    /** */
+    public static class SessionContextCacheInterceptor extends 
CacheInterceptorAdapter<Integer, String> {
+        /** */
+        @SessionContextProviderResource
+        private SessionContextProvider sessionCtxProv;
+
+        /** Replaces {@code newVal} explicitly inserted by user with {@code 
SESSION_ID} attribute value. */
+        @Override public @Nullable String onBeforePut(Cache.Entry<Integer, 
String> entry, String newVal) {
+            String val = 
sessionCtxProv.getSessionContext().getAttribute(SESSION_ID);
+
+            return val == null ? newVal : val;
+        }
+
+        /** Cancels removing entry in case {@code SESSION_ID} attribute is 
set. */
+        @Override public @Nullable IgniteBiTuple<Boolean, String> 
onBeforeRemove(Cache.Entry<Integer, String> entry) {
+            String val = 
sessionCtxProv.getSessionContext().getAttribute(SESSION_ID);
+
+            return new IgniteBiTuple<>(val != null, entry.getValue());
+        }
+    }
+
+    /** */
+    private List<List<?>> query(IgniteEx ign, String sql, Object... args) {
+        return ign.context().query().querySqlFields(new 
SqlFieldsQuery(sql).setArgs(args), false).getAll();
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoTest.java
index 89e36df4f89..5119b1f1c68 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoTest.java
@@ -26,6 +26,8 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.cache.query.annotations.QuerySqlFunction;
 import org.apache.ignite.cache.query.annotations.QuerySqlTableFunction;
@@ -38,11 +40,17 @@ import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.resources.SessionContextProviderResource;
 import org.apache.ignite.session.SessionContext;
 import org.apache.ignite.session.SessionContextProvider;
+import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assume.assumeFalse;
 
 /** */
+@RunWith(Parameterized.class)
 public class JdbcSetClientInfoTest extends GridCommonAbstractTest {
     /** */
     private static final String SESSION_ID = "sessionId";
@@ -50,6 +58,22 @@ public class JdbcSetClientInfoTest extends 
GridCommonAbstractTest {
     /** */
     private static final String URL = "jdbc:ignite:thin://127.0.0.1";
 
+    /** */
+    @Parameterized.Parameter
+    public boolean runInTx;
+
+    /** */
+    @Parameterized.Parameter(1)
+    public CacheAtomicityMode cacheMode;
+
+    /** */
+    @Parameterized.Parameters(name = "runInTx={0}, mode={1}")
+    public static Collection<Object[]> data() {
+        return GridTestUtils.cartesianProduct(
+            F.asList(false, true),
+            F.asList(CacheAtomicityMode.TRANSACTIONAL, 
CacheAtomicityMode.ATOMIC));
+    }
+
     /** {@inheritDoc} */
     @Override protected IgniteConfiguration getConfiguration(String 
instanceName) throws Exception {
         IgniteConfiguration cfg = super.getConfiguration(instanceName);
@@ -57,9 +81,22 @@ public class JdbcSetClientInfoTest extends 
GridCommonAbstractTest {
         cfg.setSqlConfiguration(new SqlConfiguration()
             .setQueryEnginesConfiguration(new 
CalciteQueryEngineConfiguration().setDefault(true)));
 
+        cfg.getTransactionConfiguration().setTxAwareQueriesEnabled(runInTx);
+
+        QueryEntity entity = new QueryEntity()
+            .setTableName("MYTABLE")
+            .setKeyType(Integer.class.getName())
+            .setValueType(String.class.getName())
+            .addQueryField("id", Integer.class.getName(), null)
+            .addQueryField("sessionId", String.class.getName(), null)
+            .setKeyFieldName("id")
+            .setValueFieldName("sessionId");
+
         cfg.setCacheConfiguration(new CacheConfiguration<>()
             .setName(DEFAULT_CACHE_NAME)
+            .setAtomicityMode(cacheMode)
             .setSqlSchema("PUBLIC")
+            .setQueryEntities(List.of(entity))
             .setSqlFunctionClasses(SessionContextFunctions.class));
 
         return cfg;
@@ -67,9 +104,9 @@ public class JdbcSetClientInfoTest extends 
GridCommonAbstractTest {
 
     /** {@inheritDoc} */
     @Override protected void beforeTest() throws Exception {
-        IgniteEx ign = startGrids(3);
+        assumeFalse(runInTx && cacheMode == CacheAtomicityMode.ATOMIC);
 
-        query(ign, "create table PUBLIC.MYTABLE(id int primary key, sessionId 
varchar);");
+        startGrids(3);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/JdbcTestSuite.java 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/JdbcTestSuite.java
index 1e9a8c4f2e4..0ca27232c9d 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/JdbcTestSuite.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/JdbcTestSuite.java
@@ -21,6 +21,7 @@ import 
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcConnectionEn
 import 
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcCrossEngineTest;
 import 
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcLocalFlagTest;
 import org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcQueryTest;
+import 
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcSetClientInfoCacheInterceptorTest;
 import 
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcSetClientInfoTest;
 import 
org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcThinTransactionalSelfTest;
 import org.junit.runner.RunWith;
@@ -35,6 +36,7 @@ import org.junit.runners.Suite;
     JdbcCrossEngineTest.class,
     JdbcThinTransactionalSelfTest.class,
     JdbcSetClientInfoTest.class,
+    JdbcSetClientInfoCacheInterceptorTest.class,
     JdbcConnectionEnabledPropertyTest.class,
     JdbcLocalFlagTest.class,
 })
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java
index bf22e0aa722..4c51863f393 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java
@@ -302,6 +302,29 @@ public class GridCacheProxyImpl<K, V> implements 
IgniteInternalCache<K, V>, Exte
         }
     }
 
+    /** @return New internal cache instance based on this one, but with 
application attributes. */
+    public GridCacheProxyImpl<K, V> withApplicationAttributes(Map<String, 
String> attrs) {
+        CacheOperationContext prev = gate.enter(opCtx);
+
+        try {
+            return new GridCacheProxyImpl<>(ctx, delegate,
+                opCtx != null ? opCtx.setApplicationAttributes(attrs) :
+                    new CacheOperationContext(
+                        false,
+                        true,
+                        false,
+                        null,
+                        false,
+                        null,
+                        false,
+                        null,
+                        attrs));
+        }
+        finally {
+            gate.leave(prev);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public <K1, V1> GridCacheProxyImpl<K1, V1> keepBinary() {
         if (opCtx != null && opCtx.isKeepBinary())
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientTxSupport.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientTxSupport.java
index 79d8e2228d3..2b15ba742dc 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientTxSupport.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientTxSupport.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.odbc;
 
+import java.util.Map;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
@@ -36,6 +37,7 @@ public interface ClientTxSupport {
      * @param isolation Transaction isolation.
      * @param timeout Transaction timeout.
      * @param lb Transaction label.
+     * @param appAttrs Application attributes.
      * @return Transaction id.
      */
     default int startClientTransaction(
@@ -43,7 +45,8 @@ public interface ClientTxSupport {
         TransactionConcurrency concurrency,
         TransactionIsolation isolation,
         long timeout,
-        String lb
+        String lb,
+        Map<String, String> appAttrs
     ) {
         GridNearTxLocal tx;
 
@@ -60,7 +63,7 @@ public interface ClientTxSupport {
                 true,
                 0,
                 lb,
-                null
+                appAttrs
             );
         }
         finally {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
index 3a4a8091183..243c831d451 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
@@ -855,7 +855,8 @@ public class JdbcRequestHandler implements 
ClientListenerRequestHandler, ClientT
             cliCtx.concurrency(),
             cliCtx.isolation(),
             cliCtx.transactionTimeout(),
-            cliCtx.transactionLabel()
+            cliCtx.transactionLabel(),
+            cliCtx.applicationAttributes()
         );
     }
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/tx/ClientTxStartRequest.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/tx/ClientTxStartRequest.java
index d32cc626f9a..a13d2a59e88 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/tx/ClientTxStartRequest.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/tx/ClientTxStartRequest.java
@@ -60,7 +60,8 @@ public class ClientTxStartRequest extends ClientRequest 
implements ClientTxSuppo
 
     /** {@inheritDoc} */
     @Override public ClientResponse process(ClientConnectionContext ctx) {
-        return new ClientIntResponse(requestId(), startClientTransaction(ctx, 
concurrency, isolation, timeout, lb));
+        // TODO IGNITE-23721: support application attributes for thin client.
+        return new ClientIntResponse(requestId(), startClientTransaction(ctx, 
concurrency, isolation, timeout, lb, null));
     }
 
     /** {@inheritDoc} */

Reply via email to