You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ti...@apache.org on 2024/12/13 12:02:17 UTC

(ignite) branch application_context updated: IGNITE-23722 Map JDBC ClientInfo to ApplicationAttributes (#11724)

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

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


The following commit(s) were added to refs/heads/application_context by this push:
     new ba33a25948d IGNITE-23722 Map JDBC ClientInfo to ApplicationAttributes (#11724)
ba33a25948d is described below

commit ba33a25948d9d8c5a8cec9db0eed25ec12f83b94
Author: Maksim Timonin <ti...@apache.org>
AuthorDate: Fri Dec 13 15:02:08 2024 +0300

    IGNITE-23722 Map JDBC ClientInfo to ApplicationAttributes (#11724)
---
 .../query/calcite/jdbc/JdbcSetClientInfoTest.java  | 291 +++++++++++++++++++++
 .../ignite/testsuites/IgniteCalciteTestSuite.java  |   2 +
 .../jdbc/thin/JdbcThinConnectionSelfTest.java      |   2 +-
 .../internal/jdbc/thin/JdbcThinConnection.java     |  28 +-
 .../odbc/jdbc/JdbcBatchExecuteRequest.java         |   2 +-
 .../odbc/jdbc/JdbcClientInfoAwareRequest.java      |  70 +++++
 .../odbc/jdbc/JdbcQueryExecuteRequest.java         |   2 +-
 .../processors/odbc/jdbc/JdbcRequestHandler.java   |   3 +
 .../processors/odbc/jdbc/JdbcThinFeature.java      |   6 +-
 .../processors/query/GridQueryProcessor.java       |   2 +
 .../processors/query/SqlClientContext.java         |  17 ++
 11 files changed, 418 insertions(+), 7 deletions(-)

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
new file mode 100644
index 00000000000..6b47c5ce7d6
--- /dev/null
+++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/jdbc/JdbcSetClientInfoTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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.SQLException;
+import java.sql.Statement;
+import java.util.List;
+import java.util.Properties;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.cache.query.annotations.QuerySqlFunction;
+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.resources.SessionContextProviderResource;
+import org.apache.ignite.session.SessionContext;
+import org.apache.ignite.session.SessionContextProvider;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+/** */
+public class JdbcSetClientInfoTest extends GridCommonAbstractTest {
+    /** */
+    private static final String SESSION_ID = "sessionId";
+
+    /** */
+    private static final String URL = "jdbc:ignite:thin://127.0.0.1";
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String instanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(instanceName);
+
+        cfg.setSqlConfiguration(new SqlConfiguration()
+            .setQueryEnginesConfiguration(new CalciteQueryEngineConfiguration().setDefault(true)));
+
+        cfg.setCacheConfiguration(new CacheConfiguration<>()
+            .setName(DEFAULT_CACHE_NAME)
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(SessionContextFunctions.class));
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        IgniteEx ign = startGrids(3);
+
+        query(ign, "create table PUBLIC.MYTABLE(id int primary key, sessionId varchar);");
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() {
+        stopAllGrids();
+    }
+
+    /** */
+    @Test
+    public void testSetClientInfo() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            checkSessionId(conn, null);
+            checkSessionId(conn, "1234");
+        }
+    }
+
+    /** */
+    @Test
+    public void testResetClientInfo() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            checkSessionId(conn, "1234");
+            checkSessionId(conn, "4567");
+            checkSessionId(conn, null);
+        }
+    }
+
+    /** */
+    @Test
+    public void testWhereClause() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            String sesId = i % 2 == 0 ? "1" : "2";
+
+            query(grid(0), "insert into PUBLIC.MYTABLE(id, sessionId) values (?, ?);", i, sesId);
+        }
+
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            for (String sesId: F.asList("1", "2")) {
+                setClientInfo(conn, sesId);
+
+                ResultSet set = jdbcQuery(conn, "select * from PUBLIC.MYTABLE where sessionId = sessionId();");
+
+                int size = 0;
+
+                while (set.next()) {
+                    String actSesId = set.getString("sessionId");
+
+                    assertEquals(sesId, actSesId);
+
+                    size++;
+                }
+
+                assertEquals(50, size);
+            }
+        }
+    }
+
+    /** */
+    @Test
+    public void testInsertClause() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            for (int i = 0; i < 100; i++) {
+                String sesId = i % 2 == 0 ? "1" : "2";
+
+                setClientInfo(conn, sesId);
+
+                jdbcQuery(conn, "insert into PUBLIC.MYTABLE(id, sessionId) values (" + i + ", sessionId());");
+            }
+        }
+
+        List<List<?>> res = query(grid(0), "select * from PUBLIC.MYTABLE where sessionId = 1");
+
+        assertEquals(50, res.size());
+
+        res = query(grid(0), "select * from PUBLIC.MYTABLE where sessionId = 2");
+
+        assertEquals(50, res.size());
+    }
+
+    /** */
+    @Test
+    public void testNestedQuery() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            String sesId = i % 2 == 0 ? "1" : "2";
+
+            query(grid(0), "insert into PUBLIC.MYTABLE(id, sessionId) values (?, ?);", i, sesId);
+        }
+
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            String sesId = "1";
+
+            setClientInfo(conn, sesId);
+
+            ResultSet set = jdbcQuery(conn, "select * from PUBLIC.MYTABLE where sessionId = (select sessionId());");
+
+            int size = 0;
+
+            while (set.next()) {
+                String actSesId = set.getString("sessionId");
+
+                assertEquals(sesId, actSesId);
+
+                size++;
+            }
+
+            assertEquals(50, size);
+        }
+    }
+
+    /** */
+    @Test
+    public void testMultipleQueries() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            setClientInfo(conn, "1");
+
+            jdbcQuery(conn,
+                "insert into PUBLIC.MYTABLE(id, sessionId) values (0, sessionId());" +
+                    "insert into PUBLIC.MYTABLE(id, sessionId) values (1, sessionId());" +
+                    "insert into PUBLIC.MYTABLE(id, sessionId) values (2, sessionId());" +
+                    "insert into PUBLIC.MYTABLE(id, sessionId) values (3, sessionId());" +
+                    "insert into PUBLIC.MYTABLE(id, sessionId) values (4, sessionId())");
+        }
+
+        List<List<?>> res = query(grid(0), "select * from PUBLIC.MYTABLE where sessionId = 1");
+
+        assertEquals(5, res.size());
+    }
+
+    /** */
+    @Test
+    public void testUpdateStatement() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            setClientInfo(conn, "1");
+
+            Statement statement = conn.createStatement();
+
+            statement.executeUpdate(
+                "insert into PUBLIC.MYTABLE(id, sessionId) values (0, sessionId());" +
+                "insert into PUBLIC.MYTABLE(id, sessionId) values (1, sessionId());" +
+                "insert into PUBLIC.MYTABLE(id, sessionId) values (2, sessionId());" +
+                "insert into PUBLIC.MYTABLE(id, sessionId) values (3, sessionId());" +
+                "insert into PUBLIC.MYTABLE(id, sessionId) values (4, sessionId())");
+        }
+
+        List<List<?>> res = query(grid(0), "select * from PUBLIC.MYTABLE where sessionId = 1");
+
+        assertEquals(5, res.size());
+    }
+
+    /** */
+    @Test
+    public void testBatchStatement() throws Exception {
+        try (Connection conn = DriverManager.getConnection(URL)) {
+            setClientInfo(conn, "1");
+
+            Statement statement = conn.createStatement();
+
+            for (int i = 0; i < 5; i++)
+                statement.addBatch("insert into PUBLIC.MYTABLE(id, sessionId) values (" + i + ", sessionId());");
+
+            statement.executeBatch();
+        }
+
+        List<List<?>> res = query(grid(0), "select * from PUBLIC.MYTABLE where sessionId = 1");
+
+        assertEquals(5, res.size());
+    }
+
+    /** */
+    private void setClientInfo(Connection conn, @Nullable String sesId) throws Exception {
+        Properties props = new Properties();
+
+        if (sesId != null)
+            props.put(SESSION_ID, sesId);
+
+        conn.setClientInfo(props);
+    }
+
+    /** */
+    private List<List<?>> query(IgniteEx ign, String sql, Object... args) {
+        return ign.context().query().querySqlFields(new SqlFieldsQuery(sql).setArgs(args), false).getAll();
+    }
+
+    /** */
+    private @Nullable ResultSet jdbcQuery(Connection conn, String sql) throws SQLException {
+        Statement statement = conn.createStatement();
+
+        boolean res = statement.execute(sql);
+
+        if (res)
+            return statement.getResultSet();
+
+        return null;
+    }
+
+    /** */
+    private void checkSessionId(Connection conn, @Nullable String sesId) throws Exception {
+        setClientInfo(conn, sesId);
+
+        ResultSet set = jdbcQuery(conn, "select sessionId() as SESSION_ID;");
+
+        set.next();
+
+        String actSesId = set.getString("SESSION_ID");
+
+        assertEquals(sesId, actSesId);
+    }
+
+    /** */
+    public static class SessionContextFunctions {
+        /** */
+        @SessionContextProviderResource
+        public SessionContextProvider sesCtxProv;
+
+        /** */
+        @QuerySqlFunction
+        public String sessionId() {
+            SessionContext sesCtx = sesCtxProv.getSessionContext();
+
+            return sesCtx == null ? null : sesCtx.getAttribute(SESSION_ID);
+        }
+    }
+}
diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
index df909b46f17..4a0cfa7e0cb 100644
--- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
+++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.processors.query.calcite.exec.LogicalRelImplem
 import org.apache.ignite.internal.processors.query.calcite.exec.NumericTypesPrecisionsTest;
 import org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteSqlFunctionsTest;
 import org.apache.ignite.internal.processors.query.calcite.exec.tracker.MemoryTrackerTest;
+import org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcSetClientInfoTest;
 import org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcThinTransactionalSelfTest;
 import org.apache.ignite.internal.processors.query.calcite.message.CalciteCommunicationMessageSerializationTest;
 import org.apache.ignite.internal.processors.query.calcite.sql.SqlCustomParserTest;
@@ -60,6 +61,7 @@ import org.junit.runners.Suite;
     SqlTransactionsUnsupportedModesTest.class,
 
     JdbcThinTransactionalSelfTest.class,
+    JdbcSetClientInfoTest.class,
 })
 public class IgniteCalciteTestSuite {
 }
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java
index ce06c333928..9a448d42d49 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java
@@ -1893,7 +1893,7 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest {
 
             assertNotNull(propsResult);
 
-            assertTrue(propsResult.isEmpty());
+            assertFalse(propsResult.isEmpty());
 
             conn.close();
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
index 0de9affbc1f..bdb9cecfad7 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinConnection.java
@@ -100,6 +100,7 @@ import org.apache.ignite.internal.processors.odbc.jdbc.JdbcBinaryTypePutRequest;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcBulkLoadBatchRequest;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcCachePartitionsRequest;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcCachePartitionsResult;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcClientInfoAwareRequest;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcOrderedBatchExecuteRequest;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcOrderedBatchExecuteResult;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcQuery;
@@ -277,6 +278,9 @@ public class JdbcThinConnection implements Connection {
     /** Marshaller context. */
     private final JdbcMarshallerContext marshCtx;
 
+    /** Client info. */
+    private @Nullable Map<String, String> clientInfo;
+
     /**
      * Creates new connection.
      *
@@ -321,7 +325,7 @@ public class JdbcThinConnection implements Connection {
         marsh.setContext(marshCtx);
 
         BinaryConfiguration binCfg = new BinaryConfiguration().setCompactFooter(true);
-        
+
         BinaryContext ctx = new BinaryContext(metaHnd, new IgniteConfiguration(), new NullLogger());
 
         ctx.configure(marsh, binCfg);
@@ -905,26 +909,41 @@ public class JdbcThinConnection implements Connection {
     @Override public void setClientInfo(String name, String val) throws SQLClientInfoException {
         if (closed)
             throw new SQLClientInfoException("Connection is closed.", null);
+
+        if (clientInfo == null)
+            clientInfo = new HashMap<>();
+
+        clientInfo.put(name, val);
     }
 
     /** {@inheritDoc} */
     @Override public void setClientInfo(Properties props) throws SQLClientInfoException {
         if (closed)
             throw new SQLClientInfoException("Connection is closed.", null);
+
+        clientInfo = new HashMap<>();
+
+        for (String propName : props.stringPropertyNames())
+            clientInfo.put(propName, props.getProperty(propName));
     }
 
     /** {@inheritDoc} */
     @Override public String getClientInfo(String name) throws SQLException {
         ensureNotClosed();
 
-        return null;
+        return clientInfo.get(name);
     }
 
     /** {@inheritDoc} */
     @Override public Properties getClientInfo() throws SQLException {
         ensureNotClosed();
 
-        return new Properties();
+        Properties ret = new Properties();
+
+        if (clientInfo != null)
+            ret.putAll(clientInfo);
+
+        return ret;
     }
 
     /** {@inheritDoc} */
@@ -1122,6 +1141,9 @@ public class JdbcThinConnection implements Connection {
                     if (req instanceof JdbcQueryExecuteRequest)
                         qryReq = (JdbcQueryExecuteRequest)req;
 
+                    if (req instanceof JdbcClientInfoAwareRequest)
+                        ((JdbcClientInfoAwareRequest)req).clientInfo(clientInfo);
+
                     JdbcResponse res = cliIo.sendRequest(req, stmt);
 
                     if (res.status() == IgniteQueryErrorCode.QUERY_CANCELED && stmt != null &&
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcBatchExecuteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcBatchExecuteRequest.java
index fe6f43b975b..12f9314f09a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcBatchExecuteRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcBatchExecuteRequest.java
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * JDBC batch execute request.
  */
-public class JdbcBatchExecuteRequest extends JdbcRequest {
+public class JdbcBatchExecuteRequest extends JdbcClientInfoAwareRequest {
     /** Schema name. */
     private String schemaName;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcClientInfoAwareRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcClientInfoAwareRequest.java
new file mode 100644
index 00000000000..98e1f8f5fdc
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcClientInfoAwareRequest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.odbc.jdbc;
+
+import java.util.Map;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.internal.binary.BinaryReaderExImpl;
+import org.apache.ignite.internal.binary.BinaryWriterExImpl;
+import org.apache.ignite.internal.jdbc.thin.JdbcThinConnection;
+import org.jetbrains.annotations.Nullable;
+
+/** Request with client info set with {@link java.sql.Connection#setClientInfo}. */
+public class JdbcClientInfoAwareRequest extends JdbcRequest {
+    /** Client info set with {@link JdbcThinConnection#setClientInfo} methods. */
+    private @Nullable Map<String, String> clientInfo;
+
+    /**
+     * @param type Command type.
+     */
+    public JdbcClientInfoAwareRequest(byte type) {
+        super(type);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeBinary(
+        BinaryWriterExImpl writer,
+        JdbcProtocolContext protoCtx
+    ) throws BinaryObjectException {
+        super.writeBinary(writer, protoCtx);
+
+        if (protoCtx.isFeatureSupported(JdbcThinFeature.CLIENT_INFO))
+            writer.writeMap(clientInfo);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readBinary(
+        BinaryReaderExImpl reader,
+        JdbcProtocolContext protoCtx
+    ) throws BinaryObjectException {
+        super.readBinary(reader, protoCtx);
+
+        if (protoCtx.isFeatureSupported(JdbcThinFeature.CLIENT_INFO))
+            clientInfo = reader.readMap();
+    }
+
+    /** @return Client info. */
+    public @Nullable Map<String, String> clientInfo() {
+        return clientInfo;
+    }
+
+    /** @param clientInfo Client info. */
+    public void clientInfo(@Nullable Map<String, String> clientInfo) {
+        this.clientInfo = clientInfo;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
index 52a839071d1..dc3d346a249 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcQueryExecuteRequest.java
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * JDBC query execute request.
  */
-public class JdbcQueryExecuteRequest extends JdbcRequest {
+public class JdbcQueryExecuteRequest extends JdbcClientInfoAwareRequest {
     /** Schema name. */
     private String schemaName;
 
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 bbcd750f587..c8754fa88ce 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
@@ -306,6 +306,9 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler, ClientT
             return new JdbcResponse(IgniteQueryErrorCode.UNKNOWN,
                 "Failed to handle JDBC request because node is stopping.");
 
+        if (req instanceof JdbcClientInfoAwareRequest)
+            cliCtx.applicationAttributes(((JdbcClientInfoAwareRequest)req).clientInfo());
+
         JdbcResponse resp;
         try {
             switch (req.type()) {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcThinFeature.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcThinFeature.java
index a51c4374cd1..f4cd046fbd6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcThinFeature.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcThinFeature.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.odbc.jdbc;
 
+import java.sql.Connection;
 import java.util.EnumSet;
 import org.apache.ignite.internal.ThinProtocolFeature;
 
@@ -36,7 +37,10 @@ public enum JdbcThinFeature implements ThinProtocolFeature {
     QUERY_TIMEOUT(3),
 
     /** Transaction aware queries. */
-    TX_AWARE_QUERIES(4);
+    TX_AWARE_QUERIES(4),
+
+    /** Send {@link Connection#setClientInfo(String, String)} with messages. */
+    CLIENT_INFO(5);
 
     /** */
     private static final EnumSet<JdbcThinFeature> ALL_FEATURES_AS_ENUM_SET = EnumSet.allOf(JdbcThinFeature.class);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index 4955418e5f7..78b47d4f0fd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -3080,6 +3080,8 @@ public class GridQueryProcessor extends GridProcessorAdapter {
                                 if (opCtx != null && opCtx.applicationAttributes() != null)
                                     sesCtx = new SessionContextImpl(opCtx.applicationAttributes());
                             }
+                            else if (cliCtx != null && cliCtx.applicationAttributes() != null)
+                                sesCtx = new SessionContextImpl(cliCtx.applicationAttributes());
 
                             QueryContext qryCtx = QueryContext.of(
                                 qry,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/SqlClientContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/SqlClientContext.java
index e8b581e0bde..3d8a33dfabc 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/SqlClientContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/SqlClientContext.java
@@ -112,6 +112,9 @@ public class SqlClientContext implements AutoCloseable {
     /** Transaction label. */
     private String lb;
 
+    /** Application attributes. */
+    private @Nullable Map<String, String> appAtts;
+
     /** Logger. */
     private final IgniteLogger log;
 
@@ -398,6 +401,20 @@ public class SqlClientContext implements AutoCloseable {
         return lb;
     }
 
+    /**
+     * @param appAtts Application attributes.
+     */
+    public void applicationAttributes(Map<String, String> appAtts) {
+        this.appAtts = appAtts;
+    }
+
+    /**
+     * @return Application attributes.
+     */
+    public Map<String, String> applicationAttributes() {
+        return appAtts;
+    }
+
     /** {@inheritDoc} */
     @Override public void close() throws Exception {
         if (streamers == null)