Skip to content

Commit

Permalink
metadata: Tarantool instance version info
Browse files Browse the repository at this point in the history
The driver can connect to different Tarantool instances within a range
of server versions. Sometimes, it's required to set a driver behaviour
depending on those versions (i.e. in scope of #213 the driver needs to
generate the functions for JDBC C4 LTRIM/RTRIM that are recognizable by
a particular Tarantool version).

This commit also includes implementation of two public DatabaseMetaData
methods getDatabaseMajorVersion and getDatabaseMinorVersion.

Affects: #213
Closes: #106
  • Loading branch information
nicktorwald committed Jan 13, 2020
1 parent 4ba88fb commit 2c4baed
Show file tree
Hide file tree
Showing 22 changed files with 278 additions and 125 deletions.
3 changes: 3 additions & 0 deletions src/main/java/org/tarantool/jdbc/SQLConstant.java
Expand Up @@ -6,5 +6,8 @@ private SQLConstant() {
}

public static final String DRIVER_NAME = "Tarantool JDBC Driver";
public static final String PRODUCT_NAME = "Tarantool";
public static final int DRIVER_MAJOR_VERSION = 4;
public static final int DRIVER_MINOR_VERSION = 2;

}
23 changes: 16 additions & 7 deletions src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java
Expand Up @@ -5,10 +5,10 @@
import org.tarantool.SqlProtoUtils;
import org.tarantool.Version;
import org.tarantool.jdbc.type.TarantoolSqlType;
import org.tarantool.util.ServerVersion;
import org.tarantool.util.TupleTwo;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.RowIdLifetime;
import java.sql.SQLException;
Expand All @@ -21,7 +21,7 @@
import java.util.Map;
import java.util.stream.Collectors;

public class SQLDatabaseMetadata implements DatabaseMetaData {
public class SQLDatabaseMetadata implements TarantoolDatabaseMetaData {

protected static final int _VSPACE = 281;
protected static final int _VINDEX = 289;
Expand Down Expand Up @@ -89,7 +89,7 @@ public boolean nullsAreSortedAtEnd() throws SQLException {

@Override
public String getDatabaseProductName() throws SQLException {
return "Tarantool";
return SQLConstant.PRODUCT_NAME;
}

@Override
Expand Down Expand Up @@ -1017,22 +1017,31 @@ public int getResultSetHoldability() throws SQLException {

@Override
public int getDatabaseMajorVersion() throws SQLException {
return 0;
return getDatabaseVersion().getMajorVersion();
}

@Override
public int getDatabaseMinorVersion() throws SQLException {
return 0;
return getDatabaseVersion().getMinorVersion();
}

@Override
public ServerVersion getDatabaseVersion() throws SQLException {
try {
return new ServerVersion(connection.getServerVersion());
} catch (Exception cause) {
throw new SQLException("Could not get the current server version number", cause);
}
}

@Override
public int getJDBCMajorVersion() throws SQLException {
return 2;
return SQLConstant.DRIVER_MAJOR_VERSION;
}

@Override
public int getJDBCMinorVersion() throws SQLException {
return 1;
return SQLConstant.DRIVER_MINOR_VERSION;
}

@Override
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/org/tarantool/jdbc/TarantoolDatabaseMetaData.java
@@ -0,0 +1,20 @@
package org.tarantool.jdbc;

import org.tarantool.util.ServerVersion;

import java.sql.DatabaseMetaData;
import java.sql.SQLException;

/**
* Tarantool specific database meta data extension.
*/
public interface TarantoolDatabaseMetaData extends DatabaseMetaData {

/**
* Gets the current Tarantool version.
*
* @return version of active connected database.
*/
ServerVersion getDatabaseVersion() throws SQLException;

}
46 changes: 21 additions & 25 deletions src/main/java/org/tarantool/protocol/ProtoUtils.java
Expand Up @@ -32,7 +32,6 @@ public abstract class ProtoUtils {
public static final int LENGTH_OF_SIZE_MESSAGE = 5;

private static final int DEFAULT_INITIAL_REQUEST_SIZE = 4096;
private static final String WELCOME = "Tarantool ";

/**
* Reads tarantool binary protocol's packet from {@code inputStream}.
Expand Down Expand Up @@ -65,7 +64,7 @@ public static TarantoolPacket readPacket(InputStream inputStream, MsgPackLite ms
*
* @param bufferReader readable channel that have to be in blocking mode
* or instance of {@link ReadableViaSelectorChannel}
* @param msgPackLite MessagePack decoder instance
* @param msgPackLite MessagePack decoder instance
*
* @return tarantool binary protocol message wrapped by instance of {@link TarantoolPacket}
*
Expand Down Expand Up @@ -120,9 +119,9 @@ public static TarantoolPacket readPacket(ReadableByteChannel bufferReader, MsgPa
/**
* Connects to a tarantool node described by {@code socket}. Performs an authentication if required
*
* @param socket a socket channel to a tarantool node
* @param username auth username
* @param password auth password
* @param socket a socket channel to a tarantool node
* @param username auth username
* @param password auth password
* @param msgPackLite MessagePack encoder / decoder instance
*
* @return object with information about a connection/
Expand All @@ -141,8 +140,7 @@ public static TarantoolGreeting connect(Socket socket,
inputStream.read(inputBytes);

String firstLine = new String(inputBytes);
assertCorrectWelcome(firstLine, socket.getRemoteSocketAddress());
String serverVersion = firstLine.substring(WELCOME.length());
TarantoolGreeting greeting = parseGreetingLine(firstLine, socket.getRemoteSocketAddress());

inputStream.read(inputBytes);
String salt = new String(inputBytes);
Expand All @@ -157,15 +155,15 @@ public static TarantoolGreeting connect(Socket socket,
assertNoErrCode(responsePacket);
}

return new TarantoolGreeting(serverVersion);
return greeting;
}

/**
* Connects to a tarantool node described by {@code socketChannel}. Performs an authentication if required.
*
* @param channel a socket channel to tarantool node. The channel have to be in blocking mode
* @param username auth username
* @param password auth password
* @param channel a socket channel to tarantool node. The channel have to be in blocking mode
* @param username auth username
* @param password auth password
* @param msgPackLite MessagePack encoder / decoder instance
*
* @return object with information about a connection/
Expand All @@ -182,10 +180,9 @@ public static TarantoolGreeting connect(SocketChannel channel,
channel.read(welcomeBytes);

String firstLine = new String(welcomeBytes.array());
assertCorrectWelcome(firstLine, channel.getRemoteAddress());
final String serverVersion = firstLine.substring(WELCOME.length());
TarantoolGreeting greeting = parseGreetingLine(firstLine, channel.getRemoteAddress());

((Buffer)welcomeBytes).clear();
((Buffer) welcomeBytes).clear();
channel.read(welcomeBytes);
String salt = new String(welcomeBytes.array());

Expand All @@ -197,17 +194,7 @@ public static TarantoolGreeting connect(SocketChannel channel,
assertNoErrCode(authResponse);
}

return new TarantoolGreeting(serverVersion);
}

private static void assertCorrectWelcome(String firstLine, SocketAddress remoteAddress) {
if (!firstLine.startsWith(WELCOME)) {
String errMsg = "Failed to connect to node " + remoteAddress.toString() +
": Welcome message should starts with tarantool but starts with '" +
firstLine +
"'";
throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet"));
}
return greeting;
}

private static void assertNoErrCode(TarantoolPacket authResponse) {
Expand Down Expand Up @@ -331,4 +318,13 @@ ByteBuffer toByteBuffer() {

}

private static TarantoolGreeting parseGreetingLine(String line, SocketAddress remoteAddress) {
try {
return new TarantoolGreeting(line);
} catch (Exception cause) {
String message = "Failed to connect to node " + remoteAddress.toString();
throw new CommunicationException(message, cause);
}
}

}
27 changes: 25 additions & 2 deletions src/main/java/org/tarantool/protocol/TarantoolGreeting.java
@@ -1,13 +1,36 @@
package org.tarantool.protocol;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TarantoolGreeting {

private static final Pattern GREETING_LINE =
Pattern.compile("Tarantool\\s+(?<version>[-.0-9a-g]+)\\s+\\((?<protocol>.*)\\)\\s+(?<uuid>[-0-9a-f]*)");

private final String serverVersion;
private final String protocolType;
private final String instanceUuid;

public TarantoolGreeting(String serverVersion) {
this.serverVersion = serverVersion;
public TarantoolGreeting(String greetingLine) {
Matcher matcher = GREETING_LINE.matcher(greetingLine);
if (!matcher.find()) {
throw new IllegalArgumentException("Welcome message '" + greetingLine + "' is incorrect ");
}
serverVersion = matcher.group("version");
protocolType = matcher.group("protocol");
instanceUuid = matcher.group("uuid");
}

public String getServerVersion() {
return serverVersion;
}

public String getProtocolType() {
return protocolType;
}

public String getInstanceUuid() {
return instanceUuid;
}
}
141 changes: 141 additions & 0 deletions src/main/java/org/tarantool/util/ServerVersion.java
@@ -0,0 +1,141 @@
package org.tarantool.util;

import java.util.Objects;

/**
* Server version holder.
*/
public class ServerVersion implements Comparable<ServerVersion> {

public static final ServerVersion V_1_9 = new ServerVersion(1, 9, 0);
public static final ServerVersion V_1_10 = new ServerVersion(1, 10, 0);
public static final ServerVersion V_2_1 = new ServerVersion(2, 1, 0);
public static final ServerVersion V_2_2 = new ServerVersion(2, 2, 0);
public static final ServerVersion V_2_2_1 = new ServerVersion(2, 2, 1);
public static final ServerVersion V_2_3 = new ServerVersion(2, 3, 0);

private final int majorVersion;
private final int minorVersion;
private final int patchVersion;

/**
* Makes a parsed server version container from
* a string in format like {@code MAJOR.MINOR.PATCH[-BUILD-gCOMMIT]}.
*
* @param version string in the Tarantool version format.
*/
public ServerVersion(String version) {
String[] parts = splitVersionParts(version);
if (parts.length < 3) {
throw new IllegalArgumentException("Expected at least major, minor, and patch version parts");
}
this.majorVersion = Integer.parseInt(parts[0]);
this.minorVersion = Integer.parseInt(parts[1]);
this.patchVersion = Integer.parseInt(parts[2]);
}

public ServerVersion(int majorVersion,
int minorVersion,
int patchVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
this.patchVersion = patchVersion;
}

public int getMajorVersion() {
return majorVersion;
}

public int getMinorVersion() {
return minorVersion;
}

public int getPatchVersion() {
return patchVersion;
}

public boolean isEqual(String versionString) {
return isEqual(new ServerVersion(versionString));
}

public boolean isEqual(ServerVersion version) {
return compareTo(version) == 0;
}

public boolean isLessOrEqualThan(String versionString) {
return isLessOrEqualThan(new ServerVersion(versionString));
}

public boolean isLessOrEqualThan(ServerVersion version) {
return compareTo(version) <= 0;
}

public boolean isGreaterOrEqualThan(String versionString) {
return isGreaterOrEqualThan(new ServerVersion(versionString));
}

public boolean isGreaterOrEqualThan(ServerVersion version) {
return compareTo(version) >= 0;
}

public boolean isGreaterThan(String versionString) {
return isGreaterThan(new ServerVersion(versionString));
}

public boolean isGreaterThan(ServerVersion version) {
return compareTo(version) > 0;
}

public boolean isLessThan(String versionString) {
return isLessThan(new ServerVersion(versionString));
}

public boolean isLessThan(ServerVersion version) {
return compareTo(version) < 0;
}

@Override
public int compareTo(ServerVersion that) {
return Integer.compare(this.toNumber(), that.toNumber());
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
ServerVersion that = (ServerVersion) object;
return majorVersion == that.majorVersion &&
minorVersion == that.minorVersion &&
patchVersion == that.patchVersion;
}

@Override
public int hashCode() {
return Objects.hash(majorVersion, minorVersion, patchVersion);
}

/**
* Translates version parts to format XXXYYYZZZ.
* For example, {@code 1.2.3} translates to number {@code 1002003}
*
* @return version as number
*/
private int toNumber() {
return (majorVersion * 1000 + minorVersion) * 1000 + patchVersion;
}

/**
* Splits Tarantool version string into parts.
* For example, {@code 2.1.1-423-g4007436aa} => {@code [2, 1, 1, 423, g4007436aa]}.
*
* @param version Tarantool version string
* @return split parts
*/
private String[] splitVersionParts(String version) {
return version.split("[.\\-]");
}
}
1 change: 1 addition & 0 deletions src/test/java/org/tarantool/ClientOperationsIT.java
Expand Up @@ -8,6 +8,7 @@

import org.tarantool.schema.TarantoolIndexNotFoundException;
import org.tarantool.schema.TarantoolSpaceNotFoundException;
import org.tarantool.util.ServerVersion;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
Expand Down

0 comments on commit 2c4baed

Please sign in to comment.