This is an automated email from the ASF dual-hosted git repository. alexpl pushed a commit to branch ignite-2.9 in repository https://gitbox.apache.org/repos/asf/ignite.git
commit 27b4eb124f46c01289655510e5b3d72b2296f2e8 Author: Aleksey Plekhanov <[email protected]> AuthorDate: Fri Sep 11 13:15:21 2020 +0300 IGNITE-13414 JDBC Thin: Compatibility fix for 2.8-2.9 versions - Fixes #8227. Signed-off-by: Aleksey Plekhanov <[email protected]> (cherry picked from commit 23cecab5a8846939dbf12ddc62c763c0b1ffa320) --- .../jdbc/JdbcThinCompatibilityTest.java | 197 +++++++++++++++++++++ .../ignite/compatibility/jdbc/package-info.java | 22 +++ .../junits/IgniteCompatibilityAbstractTest.java | 87 ++++----- .../IgniteCompatibilityBasicTestSuite.java | 4 +- .../ignite/internal/jdbc/thin/JdbcThinTcpIo.java | 21 ++- .../odbc/jdbc/JdbcConnectionContext.java | 12 +- .../processors/odbc/jdbc/JdbcRequestHandler.java | 4 +- 7 files changed, 289 insertions(+), 58 deletions(-) diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/JdbcThinCompatibilityTest.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/JdbcThinCompatibilityTest.java new file mode 100644 index 0000000..50254e6 --- /dev/null +++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/JdbcThinCompatibilityTest.java @@ -0,0 +1,197 @@ +/* + * 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.compatibility.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Arrays; +import java.util.Collection; +import org.apache.ignite.Ignite; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.compatibility.testframework.junits.Dependency; +import org.apache.ignite.compatibility.testframework.junits.IgniteCompatibilityAbstractTest; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteVersionUtils; +import org.apache.ignite.internal.util.GridJavaProcess; +import org.apache.ignite.internal.util.typedef.X; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; +import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.multijvm.IgniteProcessProxy; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Tests that current client version can connect to the server with specified version and + * specified client version can connect to the current server version. + */ +@RunWith(Parameterized.class) +public class JdbcThinCompatibilityTest extends IgniteCompatibilityAbstractTest { + /** Table name. */ + private static final String TABLE_NAME = "test_table"; + + /** URL. */ + private static final String URL = "jdbc:ignite:thin://127.0.0.1"; + + /** Rows count. */ + private static final int ROWS_CNT = 10; + + /** Parameters. */ + @Parameterized.Parameters(name = "Version {0}") + public static Iterable<String[]> versions() { + return Arrays.asList( + new String[] {"2.7.0"}, + new String[] {"2.7.5"}, + new String[] {"2.7.6"}, + new String[] {"2.8.0"}, + new String[] {"2.8.1"} + ); + } + + /** Old Ignite version. */ + @Parameterized.Parameter + public String ver; + + /** {@inheritDoc} */ + @Override protected @NotNull Collection<Dependency> getDependencies(String igniteVer) { + Collection<Dependency> dependencies = super.getDependencies(igniteVer); + + dependencies.add(new Dependency("indexing", "ignite-indexing", false)); + + return dependencies; + } + + /** + * @throws Exception If failed. + */ + @Test + public void testOldClientToCurrentServer() throws Exception { + try (Ignite ignite = startGrid(0)) { + initTable(ignite); + + GridJavaProcess proc = GridJavaProcess.exec( + JdbcThinQueryRunner.class.getName(), + null, + log, + log::info, + null, + null, + getProcessProxyJvmArgs(ver), + null + ); + + try { + GridTestUtils.waitForCondition(() -> !proc.getProcess().isAlive(), 5_000L); + + assertEquals(0, proc.getProcess().exitValue()); + } + finally { + if (proc.getProcess().isAlive()) + proc.kill(); + } + } + } + + /** + * @throws Exception If failed. + */ + @Test + public void testCurrentClientToOldServer() throws Exception { + IgniteProcessProxy proxy = null; + + try { + Ignite ignite = startGrid(1, ver, + cfg -> cfg + .setLocalHost("127.0.0.1") + .setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(new TcpDiscoveryVmIpFinder(true))), + JdbcThinCompatibilityTest::initTable); + + proxy = IgniteProcessProxy.ignite(ignite.name()); + + testJdbcQuery(); + } + finally { + stopAllGrids(); + + if (proxy != null) { + Process proc = proxy.getProcess().getProcess(); + + // We should wait until process exits, or it can affect next tests. + GridTestUtils.waitForCondition(() -> !proc.isAlive(), 5_000L); + } + } + } + + /** Execute sql. */ + private static void executeSql(IgniteEx igniteEx, String sql) { + igniteEx.context().query().querySqlFields(new SqlFieldsQuery(sql), false).getAll(); + } + + /** */ + private static void initTable(Ignite ignite) { + IgniteEx igniteEx = (IgniteEx)ignite; + + executeSql(igniteEx, "CREATE TABLE " + TABLE_NAME + " (id int primary key, name varchar)"); + + for (int i = 0; i < ROWS_CNT; i++) + executeSql(igniteEx, "INSERT INTO " + TABLE_NAME + " (id, name) VALUES(" + i + ", 'name" + i + "')"); + } + + /** */ + private static void testJdbcQuery() throws Exception { + try (Connection conn = DriverManager.getConnection(URL); Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery("SELECT id, name FROM " + TABLE_NAME + " ORDER BY id"); + + assertNotNull(rs); + + int cnt = 0; + + while (rs.next()) { + int id = rs.getInt("id"); + String name = rs.getString("name"); + + assertEquals(cnt, id); + assertEquals("name" + cnt, name); + + cnt++; + } + + assertEquals(ROWS_CNT, cnt); + } + } + + /** + * Runner class to test query from remote JVM process with old Ignite version as dependencies in class path. + */ + public static class JdbcThinQueryRunner { + /** */ + public static void main(String[] args) throws Exception { + X.println(GridJavaProcess.PID_MSG_PREFIX + U.jvmPid()); + X.println("Start JDBC connection with Ignite version: " + IgniteVersionUtils.VER); + + testJdbcQuery(); + + X.println("Success"); + } + } +} diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/package-info.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/package-info.java new file mode 100644 index 0000000..50d961b --- /dev/null +++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/jdbc/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Contains compatibility tests related to JDBC. + */ + +package org.apache.ignite.compatibility.jdbc; diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java index 2b5fd3c..e3f693a 100644 --- a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java +++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testframework/junits/IgniteCompatibilityAbstractTest.java @@ -159,46 +159,7 @@ public abstract class IgniteCompatibilityAbstractTest extends GridCommonAbstract } @Override protected Collection<String> filteredJvmArgs() throws Exception { - Collection<String> filteredJvmArgs = new ArrayList<>(); - - filteredJvmArgs.add("-ea"); - - for (String arg : U.jvmArgs()) { - if (arg.startsWith("-Xmx") || arg.startsWith("-Xms")) - filteredJvmArgs.add(arg); - } - - final Collection<Dependency> dependencies = getDependencies(ver); - - Set<String> excluded = getExcluded(ver, dependencies); - - StringBuilder pathBuilder = new StringBuilder(); - - for (URL url : CompatibilityTestsUtils.classLoaderUrls(CLASS_LOADER)) { - String path = url.getPath(); - - if (excluded.stream().noneMatch(path::contains)) - pathBuilder.append(path).append(File.pathSeparator); - } - - for (Dependency dependency : dependencies) { - final String artifactVer = Optional.ofNullable(dependency.version()).orElse(ver); - - String pathToArtifact = MavenUtils.getPathToIgniteArtifact(dependency.groupId(), - dependency.artifactId(), artifactVer, dependency.classifier()); - - pathBuilder.append(pathToArtifact).append(File.pathSeparator); - } - - filteredJvmArgs.add("-cp"); - filteredJvmArgs.add(pathBuilder.toString()); - - final Collection<String> jvmParms = getJvmParams(); - - if (jvmParms != null) - filteredJvmArgs.addAll(jvmParms); - - return filteredJvmArgs; + return getProcessProxyJvmArgs(ver); } }; @@ -227,6 +188,52 @@ public abstract class IgniteCompatibilityAbstractTest extends GridCommonAbstract } /** + * Creates list of JVM arguments to be used to start new Ignite process in separate JVM. + */ + protected Collection<String> getProcessProxyJvmArgs(String ver) throws Exception { + Collection<String> filteredJvmArgs = new ArrayList<>(); + + filteredJvmArgs.add("-ea"); + + for (String arg : U.jvmArgs()) { + if (arg.startsWith("-Xmx") || arg.startsWith("-Xms")) + filteredJvmArgs.add(arg); + } + + final Collection<Dependency> dependencies = getDependencies(ver); + + Set<String> excluded = getExcluded(ver, dependencies); + + StringBuilder pathBuilder = new StringBuilder(); + + for (URL url : CompatibilityTestsUtils.classLoaderUrls(CLASS_LOADER)) { + String path = url.getPath(); + + if (excluded.stream().noneMatch(path::contains)) + pathBuilder.append(path).append(File.pathSeparator); + } + + for (Dependency dependency : dependencies) { + final String artifactVer = Optional.ofNullable(dependency.version()).orElse(ver); + + String pathToArtifact = MavenUtils.getPathToIgniteArtifact(dependency.groupId(), + dependency.artifactId(), artifactVer, dependency.classifier()); + + pathBuilder.append(pathToArtifact).append(File.pathSeparator); + } + + filteredJvmArgs.add("-cp"); + filteredJvmArgs.add(pathBuilder.toString()); + + final Collection<String> jvmParms = getJvmParams(); + + if (jvmParms != null) + filteredJvmArgs.addAll(jvmParms); + + return filteredJvmArgs; + } + + /** * Total amount of milliseconds. * * @return timeout in ms. diff --git a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java index 27fdb1a..4da7401a 100644 --- a/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java +++ b/modules/compatibility/src/test/java/org/apache/ignite/compatibility/testsuites/IgniteCompatibilityBasicTestSuite.java @@ -18,6 +18,7 @@ package org.apache.ignite.compatibility.testsuites; import org.apache.ignite.compatibility.cache.LocalCacheTest; +import org.apache.ignite.compatibility.jdbc.JdbcThinCompatibilityTest; import org.apache.ignite.compatibility.persistence.FoldersReuseCompatibilityTest; import org.apache.ignite.compatibility.persistence.MetaStorageCompatibilityTest; import org.apache.ignite.compatibility.persistence.MigratingToWalV2SerializerWithCompactionTest; @@ -36,7 +37,8 @@ import org.junit.runners.Suite; MigratingToWalV2SerializerWithCompactionTest.class, MetaStorageCompatibilityTest.class, LocalCacheTest.class, - MoveBinaryMetadataCompatibility.class + MoveBinaryMetadataCompatibility.class, + JdbcThinCompatibilityTest.class }) public class IgniteCompatibilityBasicTestSuite { } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java index 9129666..b8c5cd4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinTcpIo.java @@ -94,11 +94,11 @@ public class JdbcThinTcpIo { /** Version 2.8.0. */ private static final ClientListenerProtocolVersion VER_2_8_0 = ClientListenerProtocolVersion.create(2, 8, 0); - /** Version 2.8.1. Adds features flags support. */ - private static final ClientListenerProtocolVersion VER_2_8_1 = ClientListenerProtocolVersion.create(2, 8, 1); + /** Version 2.9.0. Adds user attributes support. Adds features flags support. */ + private static final ClientListenerProtocolVersion VER_2_9_0 = ClientListenerProtocolVersion.create(2, 9, 0); /** Current version. */ - private static final ClientListenerProtocolVersion CURRENT_VER = VER_2_8_1; + private static final ClientListenerProtocolVersion CURRENT_VER = VER_2_9_0; /** Initial output stream capacity for handshake. */ private static final int HANDSHAKE_MSG_SIZE = 13; @@ -285,7 +285,9 @@ public class JdbcThinTcpIo { writer.writeByte(nullableBooleanToByte(connProps.isDataPageScanEnabled())); JdbcUtils.writeNullableInteger(writer, connProps.getUpdateBatchSize()); + } + if (ver.compareTo(VER_2_9_0) >= 0) { String userAttrs = connProps.getUserAttributesFactory(); if (F.isEmpty(userAttrs)) @@ -304,10 +306,9 @@ public class JdbcThinTcpIo { SqlStateCode.CLIENT_CONNECTION_FAILED, e); } } - } - if (ver.compareTo(VER_2_8_1) >= 0) writer.writeByteArray(ThinProtocolFeature.featuresAsBytes(enabledFeatures())); + } if (!F.isEmpty(connProps.getUsername())) { assert ver.compareTo(VER_2_5_0) >= 0 : "Authentication is supported since 2.5"; @@ -341,7 +342,7 @@ public class JdbcThinTcpIo { handshakeRes.igniteVersion(new IgniteProductVersion(maj, min, maintenance, stage, ts, hash)); - if (ver.compareTo(VER_2_8_1) >= 0) { + if (ver.compareTo(VER_2_9_0) >= 0) { byte[] srvFeatures = reader.readByteArray(); EnumSet<JdbcThinFeature> features = JdbcThinFeature.enumSet(srvFeatures); @@ -373,7 +374,8 @@ public class JdbcThinTcpIo { + ", url=" + connProps.getUrl() + " address=" + sockAddr + ']', SqlStateCode.CONNECTION_REJECTED); } - if (VER_2_7_0.equals(srvProtoVer0) + if (VER_2_8_0.equals(srvProtoVer0) + || VER_2_7_0.equals(srvProtoVer0) || VER_2_5_0.equals(srvProtoVer0) || VER_2_4_0.equals(srvProtoVer0) || VER_2_3_0.equals(srvProtoVer0) @@ -736,8 +738,9 @@ public class JdbcThinTcpIo { } /** - * Set of features enabled on clien side. To get features - * supported by both sides use {@link #protoCtx}. + * Set of features enabled on client side. To get features supported by both sides use {@link #protoCtx}. + * Since {@link #protoCtx} only available after handshake, any handshake protocol change should not use features, + * but increment protocol version. */ private EnumSet<JdbcThinFeature> enabledFeatures() { EnumSet<JdbcThinFeature> features = JdbcThinFeature.allFeaturesAsEnumSet(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java index 0f8fdc1..e7f2120 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java @@ -65,11 +65,11 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte /** Version 2.8.0: adds query id in order to implement cancel feature, partition awareness support: IEP-23.*/ static final ClientListenerProtocolVersion VER_2_8_0 = ClientListenerProtocolVersion.create(2, 8, 0); - /** Version 2.8.1: adds features flags support.*/ - static final ClientListenerProtocolVersion VER_2_8_1 = ClientListenerProtocolVersion.create(2, 8, 1); + /** Version 2.9.0: adds user attributes, adds features flags support. */ + static final ClientListenerProtocolVersion VER_2_9_0 = ClientListenerProtocolVersion.create(2, 9, 0); /** Current version. */ - public static final ClientListenerProtocolVersion CURRENT_VER = VER_2_8_1; + public static final ClientListenerProtocolVersion CURRENT_VER = VER_2_9_0; /** Supported versions. */ private static final Set<ClientListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>(); @@ -97,7 +97,7 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte static { SUPPORTED_VERS.add(CURRENT_VER); - SUPPORTED_VERS.add(VER_2_8_1); + SUPPORTED_VERS.add(VER_2_9_0); SUPPORTED_VERS.add(VER_2_8_0); SUPPORTED_VERS.add(VER_2_7_0); SUPPORTED_VERS.add(VER_2_5_0); @@ -179,11 +179,11 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte dataPageScanEnabled = nullableBooleanFromByte(reader.readByte()); updateBatchSize = JdbcUtils.readNullableInteger(reader); + } + if (ver.compareTo(VER_2_9_0) >= 0) { userAttrs = reader.readMap(); - } - if (ver.compareTo(VER_2_8_1) >= 0) { byte[] cliFeatures = reader.readByteArray(); features = JdbcThinFeature.enumSet(cliFeatures); 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 aeedfcf..a42f770 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 @@ -91,7 +91,7 @@ import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionCont import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_4_0; import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_7_0; import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_8_0; -import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_8_1; +import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_9_0; import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.BATCH_EXEC; import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.BATCH_EXEC_ORDERED; import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcRequest.BINARY_TYPE_GET; @@ -534,7 +534,7 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler { writer.writeUuid(connCtx.kernalContext().localNodeId()); // Write all features supported by the node. - if (protocolVer.compareTo(VER_2_8_1) >= 0) + if (protocolVer.compareTo(VER_2_9_0) >= 0) writer.writeByteArray(ThinProtocolFeature.featuresAsBytes(connCtx.protocolContext().features())); }
