Browse Source

SQL: Enforce and document dedicated client version compatibility (#70451)

* Implement dedicated client version compatibility

Add further dedicated client (xDBC, CLI) compatibility rules and
document these. A client is version-compatible with the server if:
- it supports version compatibility (past or on 7.7.0); and
- it's not on a version newer than server's; and
- it's major version is at most one unit behind server's.

Co-authored-by: James Rodewig <40268737+jrodewig@users.noreply.github.com>
Bogdan Pintea 4 years ago
parent
commit
925f1645cf

+ 8 - 2
docs/reference/sql/endpoints/jdbc.asciidoc

@@ -40,11 +40,17 @@ or from `artifacts.elastic.co/maven` by adding it to the repositories list:
 </repositories>
 ----
 
+[[jdbc-compatibility]]
+[discrete]
+=== Version compatibility
+
+include::version-compat.asciidoc[]
+
 [[jdbc-setup]]
 [discrete]
 === Setup
 
-The driver main class is `org.elasticsearch.xpack.sql.jdbc.EsDriver`. 
+The driver main class is `org.elasticsearch.xpack.sql.jdbc.EsDriver`.
 Note the driver  implements the JDBC 4.0 +Service Provider+ mechanism meaning it is registered automatically
 as long as it is available in the classpath.
 
@@ -75,7 +81,7 @@ The driver recognized the following properties:
 ===== Essential
 [[jdbc-cfg-timezone]]
 `timezone` (default JVM timezone)::
-Timezone used by the driver _per connection_ indicated by its `ID`. 
+Timezone used by the driver _per connection_ indicated by its `ID`.
 *Highly* recommended to set it (to, say, `UTC`) as the JVM timezone can vary, is global for the entire JVM and can't be changed easily when running under a security manager.
 
 [[jdbc-cfg-network]]

+ 7 - 2
docs/reference/sql/endpoints/odbc/installation.asciidoc

@@ -8,12 +8,12 @@ The {odbc} can be installed on Microsoft Windows using an MSI package. The insta
 [[prerequisites]]
 ==== Installation Prerequisites
 
-The recommended installation platform is Windows 10 64 bit _or_ Windows Server 2016 64 bit.
+The recommended installation platform is Windows 10 64 bit or Windows Server 2016 64 bit.
 
 Before you install the {odbc} you need to meet the following prerequisites;
 
 * .NET Framework 4.0 full - https://www.microsoft.com/en-au/download/details.aspx?id=17718
-* Microsoft Visual C++ Redistributable for Visual Studio 2017 - https://support.microsoft.com/en-au/help/2977003/the-latest-supported-visual-c-downloads
+* Microsoft Visual C++ Redistributable for Visual Studio 2017 or later - https://support.microsoft.com/en-au/help/2977003/the-latest-supported-visual-c-downloads
 - The 64 bit driver requires the x64 redistributable
 - The 32 bit driver requires the x86 or the x64 redistributable (the latter also installs the components needed for the 32 bit driver)
 * Elevated privileges (administrator) for the User performing the installation.
@@ -27,6 +27,11 @@ about running an unrecognized app. If the MSI has been downloaded from
 Elastic's web site, it is safe to acknowledge the message by allowing the
 installation to continue (`Run anyway`).
 
+[[odbc-compatibility]]
+==== Version compatibility
+
+include::../version-compat.asciidoc[]
+
 [[download]]
 ==== Download the `.msi` package(s)
 

+ 49 - 0
docs/reference/sql/endpoints/version-compat.asciidoc

@@ -0,0 +1,49 @@
+Your driver must be compatible with your {es} server version.
+
+IMPORTANT: The driver version cannot be newer than the {es} server version.
+For example, A 7.10.0 server is not compatible with {version} drivers.
+
+[options="header",cols="1,3a,1"]
+|====
+| {es} server version
+| Compatible driver versions
+| Example
+
+ifeval::[ "{major-version}" != "7.x" ]
+
+ifeval::[ "{minor-version}" != "8.0" ]
+| 8.0.0–{version}
+| * The same version
+  * Any earlier 8.x version
+  * Any 7.x version after 7.7.0.
+| An {version} server is compatible with {version} and earlier 8.x drivers. An
+{version} server is also compatible with 7.7.0 and later 7.x drivers.
+endif::[]
+
+ifeval::[ "{minor-version}" == "8.0" ]
+| 8.0.0
+| * The same version
+  * Any 7.x version after 7.7.0.
+| An 8.0.0 server is compatible with 8.0.0 drivers. An 8.0.0 server is also
+compatible with 7.7.0 and later 7.x drivers.
+endif::[]
+
+// After 8.0 release, replace 7.x with last 7.x version
+| 7.7.1-7.x
+| * The same version
+  * An earlier 7.x version, back to 7.7.0.
+| A 7.10.0 server is compatible with 7.7.0-7.10.0 drivers.
+
+endif::[]
+
+ifeval::[ "{major-version}" == "7.x" ]
+| 7.7.1-{version}
+| * The same version
+  * An earlier 7.x version, back to 7.7.0.
+| A 7.10.0 server is compatible with 7.7.0-7.10.0 drivers.
+endif::[]
+
+| 7.7.0 and earlier versions
+| * The same version.
+| A 7.6.1 server is only compatible with 7.6.1 drivers.
+|====

+ 3 - 3
x-pack/plugin/sql/sql-action/src/main/java/org/elasticsearch/xpack/sql/action/AbstractSqlQueryRequest.java

@@ -6,7 +6,6 @@
  */
 package org.elasticsearch.xpack.sql.action;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.CompositeIndicesRequest;
 import org.elasticsearch.common.Nullable;
@@ -38,6 +37,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.function.Supplier;
 
+import static org.elasticsearch.Version.CURRENT;
 import static org.elasticsearch.action.ValidateActions.addValidationError;
 import static org.elasticsearch.xpack.sql.proto.Protocol.CLIENT_ID_NAME;
 import static org.elasticsearch.xpack.sql.proto.Protocol.VERSION_NAME;
@@ -236,9 +236,9 @@ public abstract class AbstractSqlQueryRequest extends AbstractSqlRequest impleme
                     validationException = addValidationError("[version] is required for the [" + mode.toString() + "] client",
                         validationException);
                 }
-            } else if (SqlVersion.isClientCompatible(requestInfo().version()) == false) {
+            } else if (SqlVersion.isClientCompatible(SqlVersion.fromId(CURRENT.id), requestInfo().version()) == false) {
                 validationException = addValidationError("The [" + requestInfo().version() + "] version of the [" +
-                        mode.toString() + "] " + "client is not compatible with Elasticsearch version [" + Version.CURRENT + "]",
+                        mode.toString() + "] " + "client is not compatible with Elasticsearch version [" + CURRENT + "]",
                     validationException);
             }
         }

+ 3 - 3
x-pack/plugin/sql/sql-action/src/main/java/org/elasticsearch/xpack/sql/action/SqlQueryResponse.java

@@ -12,7 +12,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionResponse;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.Strings;
@@ -26,6 +25,7 @@ import org.elasticsearch.xpack.sql.proto.SqlVersion;
 import org.elasticsearch.xpack.sql.proto.StringUtils;
 
 import static java.util.Collections.unmodifiableList;
+import static org.elasticsearch.Version.CURRENT;
 import static org.elasticsearch.xpack.sql.action.AbstractSqlQueryRequest.CURSOR;
 import static org.elasticsearch.xpack.sql.proto.Mode.CLI;
 import static org.elasticsearch.xpack.sql.proto.Mode.JDBC;
@@ -87,7 +87,7 @@ public class SqlQueryResponse extends ActionResponse implements ToXContentObject
     ) {
         this.cursor = cursor;
         this.mode = mode;
-        this.sqlVersion = sqlVersion != null ? sqlVersion : fromId(Version.CURRENT.id);
+        this.sqlVersion = sqlVersion != null ? sqlVersion : fromId(CURRENT.id);
         this.columnar = columnar;
         this.columns = columns;
         this.rows = rows;
@@ -216,7 +216,7 @@ public class SqlQueryResponse extends ActionResponse implements ToXContentObject
         if (value instanceof ZonedDateTime) {
             ZonedDateTime zdt = (ZonedDateTime) value;
             // use the ISO format
-            if (mode == JDBC && isClientCompatible(sqlVersion)) {
+            if (mode == JDBC && isClientCompatible(SqlVersion.fromId(CURRENT.id), sqlVersion)) {
                 builder.value(StringUtils.toString(zdt, sqlVersion));
             } else {
                 builder.value(StringUtils.toString(zdt));

+ 7 - 3
x-pack/plugin/sql/sql-proto/src/main/java/org/elasticsearch/xpack/sql/proto/SqlVersion.java

@@ -159,9 +159,13 @@ public class SqlVersion implements Comparable<SqlVersion>{
         return version.compareTo(V_7_7_0) >= 0;
     }
 
-    public static boolean isClientCompatible(SqlVersion version) {
-        /* only client's of version 7.7.0 and later are supported as backwards compatible */
-        return V_7_7_0.compareTo(version) <= 0;
+    // A client is version-compatible with the server if:
+    // - it supports version compatibility (past or on 7.7.0); and
+    // - it's not on a version newer than server's; and
+    // - it's major version is at most one unit behind server's.
+    public static boolean isClientCompatible(SqlVersion server, SqlVersion client) {
+        // ES's Version.CURRENT not available (core not a dependency), so it needs to be passed in as a parameter.
+        return hasVersionCompatibility(client) && server.compareTo(client) >= 0 && server.major - client.major <= 1;
     }
 
     public static boolean supportsDateNanos(SqlVersion version) {

+ 34 - 1
x-pack/plugin/sql/sql-proto/src/test/java/org/elasticsearch/xpack/sql/proto/SqlVersionTests.java

@@ -9,9 +9,12 @@ package org.elasticsearch.xpack.sql.proto;
 
 import org.elasticsearch.test.ESTestCase;
 
+import static org.elasticsearch.Version.CURRENT;
 import static org.elasticsearch.xpack.sql.proto.SqlVersion.MAJOR_MULTIPLIER;
 import static org.elasticsearch.xpack.sql.proto.SqlVersion.MINOR_MULTIPLIER;
 import static org.elasticsearch.xpack.sql.proto.SqlVersion.REVISION_MULTIPLIER;
+import static org.elasticsearch.xpack.sql.proto.SqlVersion.V_7_7_0;
+import static org.elasticsearch.xpack.sql.proto.SqlVersion.isClientCompatible;
 
 public class SqlVersionTests extends ESTestCase {
     public void test123FromString() {
@@ -45,7 +48,7 @@ public class SqlVersionTests extends ESTestCase {
     }
 
     public void testFromId() {
-        SqlVersion ver = new SqlVersion((byte)randomIntBetween(0, 99), (byte)randomIntBetween(0, 99), (byte)randomIntBetween(0, 99));
+        SqlVersion ver = new SqlVersion((byte) randomIntBetween(0, 99), (byte) randomIntBetween(0, 99), (byte) randomIntBetween(0, 99));
         assertEquals(ver, SqlVersion.fromId(ver.id));
     }
 
@@ -73,4 +76,34 @@ public class SqlVersionTests extends ESTestCase {
         assertNotEquals(ver1, ver2);
     }
 
+    public void testVersionCompatibilityClientWithNoCompatibility() {
+        SqlVersion server = SqlVersion.fromId(CURRENT.id);
+        int major = randomIntBetween(1, 7);
+        SqlVersion client = new SqlVersion(major, randomIntBetween(0, major == 7 ? 6 : 99), randomIntBetween(0, 99));
+        assertFalse(isClientCompatible(server, client));
+    }
+
+    public void testVersionCompatibilityClientNewer() {
+        int major = randomIntBetween(7, 99);
+        SqlVersion server = new SqlVersion(major, randomIntBetween(major > 7 ? 0 : 7, 99), randomIntBetween(0, 98));
+        SqlVersion client = new SqlVersion(server.major, server.minor, (byte) (server.revision + 1));
+        assertFalse(isClientCompatible(server, client));
+    }
+
+    public void testVersionCompatibilityClientTooOld() {
+        int major = randomIntBetween(9, 99);
+        SqlVersion server = new SqlVersion(major, randomIntBetween(0, 99), randomIntBetween(0, 99));
+        SqlVersion client = new SqlVersion(major - 2, randomIntBetween(0, 99), randomIntBetween(0, 99));
+        assertFalse(isClientCompatible(server, client));
+    }
+
+    public void testVersionCompatibile() {
+        SqlVersion client = new SqlVersion(randomIntBetween(V_7_7_0.major, 99 - 1), randomIntBetween(V_7_7_0.minor, 99),
+            randomIntBetween(0, 99));
+        int serverMajor = client.major + (randomBoolean() ? 0 : 1);
+        int serverMinor = randomIntBetween(client.major == serverMajor ? client.minor : 0, 99);
+        int serverRevision = randomIntBetween(client.major == serverMajor && client.minor == serverMinor ? client.revision : 0, 99);
+        SqlVersion server = new SqlVersion(serverMajor, serverMinor, serverRevision);
+        assertTrue(isClientCompatible(server, client));
+    }
 }