Skip to content

Commit 2c4baed

Browse files
committed
metadata: Tarantool instance version info
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
1 parent 4ba88fb commit 2c4baed

22 files changed

+278
-125
lines changed

src/main/java/org/tarantool/jdbc/SQLConstant.java

+3
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ private SQLConstant() {
66
}
77

88
public static final String DRIVER_NAME = "Tarantool JDBC Driver";
9+
public static final String PRODUCT_NAME = "Tarantool";
10+
public static final int DRIVER_MAJOR_VERSION = 4;
11+
public static final int DRIVER_MINOR_VERSION = 2;
912

1013
}

src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java

+16-7
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import org.tarantool.SqlProtoUtils;
66
import org.tarantool.Version;
77
import org.tarantool.jdbc.type.TarantoolSqlType;
8+
import org.tarantool.util.ServerVersion;
89
import org.tarantool.util.TupleTwo;
910

1011
import java.sql.Connection;
11-
import java.sql.DatabaseMetaData;
1212
import java.sql.ResultSet;
1313
import java.sql.RowIdLifetime;
1414
import java.sql.SQLException;
@@ -21,7 +21,7 @@
2121
import java.util.Map;
2222
import java.util.stream.Collectors;
2323

24-
public class SQLDatabaseMetadata implements DatabaseMetaData {
24+
public class SQLDatabaseMetadata implements TarantoolDatabaseMetaData {
2525

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

9090
@Override
9191
public String getDatabaseProductName() throws SQLException {
92-
return "Tarantool";
92+
return SQLConstant.PRODUCT_NAME;
9393
}
9494

9595
@Override
@@ -1017,22 +1017,31 @@ public int getResultSetHoldability() throws SQLException {
10171017

10181018
@Override
10191019
public int getDatabaseMajorVersion() throws SQLException {
1020-
return 0;
1020+
return getDatabaseVersion().getMajorVersion();
10211021
}
10221022

10231023
@Override
10241024
public int getDatabaseMinorVersion() throws SQLException {
1025-
return 0;
1025+
return getDatabaseVersion().getMinorVersion();
1026+
}
1027+
1028+
@Override
1029+
public ServerVersion getDatabaseVersion() throws SQLException {
1030+
try {
1031+
return new ServerVersion(connection.getServerVersion());
1032+
} catch (Exception cause) {
1033+
throw new SQLException("Could not get the current server version number", cause);
1034+
}
10261035
}
10271036

10281037
@Override
10291038
public int getJDBCMajorVersion() throws SQLException {
1030-
return 2;
1039+
return SQLConstant.DRIVER_MAJOR_VERSION;
10311040
}
10321041

10331042
@Override
10341043
public int getJDBCMinorVersion() throws SQLException {
1035-
return 1;
1044+
return SQLConstant.DRIVER_MINOR_VERSION;
10361045
}
10371046

10381047
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.tarantool.jdbc;
2+
3+
import org.tarantool.util.ServerVersion;
4+
5+
import java.sql.DatabaseMetaData;
6+
import java.sql.SQLException;
7+
8+
/**
9+
* Tarantool specific database meta data extension.
10+
*/
11+
public interface TarantoolDatabaseMetaData extends DatabaseMetaData {
12+
13+
/**
14+
* Gets the current Tarantool version.
15+
*
16+
* @return version of active connected database.
17+
*/
18+
ServerVersion getDatabaseVersion() throws SQLException;
19+
20+
}

src/main/java/org/tarantool/protocol/ProtoUtils.java

+21-25
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public abstract class ProtoUtils {
3232
public static final int LENGTH_OF_SIZE_MESSAGE = 5;
3333

3434
private static final int DEFAULT_INITIAL_REQUEST_SIZE = 4096;
35-
private static final String WELCOME = "Tarantool ";
3635

3736
/**
3837
* Reads tarantool binary protocol's packet from {@code inputStream}.
@@ -65,7 +64,7 @@ public static TarantoolPacket readPacket(InputStream inputStream, MsgPackLite ms
6564
*
6665
* @param bufferReader readable channel that have to be in blocking mode
6766
* or instance of {@link ReadableViaSelectorChannel}
68-
* @param msgPackLite MessagePack decoder instance
67+
* @param msgPackLite MessagePack decoder instance
6968
*
7069
* @return tarantool binary protocol message wrapped by instance of {@link TarantoolPacket}
7170
*
@@ -120,9 +119,9 @@ public static TarantoolPacket readPacket(ReadableByteChannel bufferReader, MsgPa
120119
/**
121120
* Connects to a tarantool node described by {@code socket}. Performs an authentication if required
122121
*
123-
* @param socket a socket channel to a tarantool node
124-
* @param username auth username
125-
* @param password auth password
122+
* @param socket a socket channel to a tarantool node
123+
* @param username auth username
124+
* @param password auth password
126125
* @param msgPackLite MessagePack encoder / decoder instance
127126
*
128127
* @return object with information about a connection/
@@ -141,8 +140,7 @@ public static TarantoolGreeting connect(Socket socket,
141140
inputStream.read(inputBytes);
142141

143142
String firstLine = new String(inputBytes);
144-
assertCorrectWelcome(firstLine, socket.getRemoteSocketAddress());
145-
String serverVersion = firstLine.substring(WELCOME.length());
143+
TarantoolGreeting greeting = parseGreetingLine(firstLine, socket.getRemoteSocketAddress());
146144

147145
inputStream.read(inputBytes);
148146
String salt = new String(inputBytes);
@@ -157,15 +155,15 @@ public static TarantoolGreeting connect(Socket socket,
157155
assertNoErrCode(responsePacket);
158156
}
159157

160-
return new TarantoolGreeting(serverVersion);
158+
return greeting;
161159
}
162160

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

184182
String firstLine = new String(welcomeBytes.array());
185-
assertCorrectWelcome(firstLine, channel.getRemoteAddress());
186-
final String serverVersion = firstLine.substring(WELCOME.length());
183+
TarantoolGreeting greeting = parseGreetingLine(firstLine, channel.getRemoteAddress());
187184

188-
((Buffer)welcomeBytes).clear();
185+
((Buffer) welcomeBytes).clear();
189186
channel.read(welcomeBytes);
190187
String salt = new String(welcomeBytes.array());
191188

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

200-
return new TarantoolGreeting(serverVersion);
201-
}
202-
203-
private static void assertCorrectWelcome(String firstLine, SocketAddress remoteAddress) {
204-
if (!firstLine.startsWith(WELCOME)) {
205-
String errMsg = "Failed to connect to node " + remoteAddress.toString() +
206-
": Welcome message should starts with tarantool but starts with '" +
207-
firstLine +
208-
"'";
209-
throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet"));
210-
}
197+
return greeting;
211198
}
212199

213200
private static void assertNoErrCode(TarantoolPacket authResponse) {
@@ -331,4 +318,13 @@ ByteBuffer toByteBuffer() {
331318

332319
}
333320

321+
private static TarantoolGreeting parseGreetingLine(String line, SocketAddress remoteAddress) {
322+
try {
323+
return new TarantoolGreeting(line);
324+
} catch (Exception cause) {
325+
String message = "Failed to connect to node " + remoteAddress.toString();
326+
throw new CommunicationException(message, cause);
327+
}
328+
}
329+
334330
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,36 @@
11
package org.tarantool.protocol;
22

3+
import java.util.regex.Matcher;
4+
import java.util.regex.Pattern;
5+
36
public class TarantoolGreeting {
7+
8+
private static final Pattern GREETING_LINE =
9+
Pattern.compile("Tarantool\\s+(?<version>[-.0-9a-g]+)\\s+\\((?<protocol>.*)\\)\\s+(?<uuid>[-0-9a-f]*)");
10+
411
private final String serverVersion;
12+
private final String protocolType;
13+
private final String instanceUuid;
514

6-
public TarantoolGreeting(String serverVersion) {
7-
this.serverVersion = serverVersion;
15+
public TarantoolGreeting(String greetingLine) {
16+
Matcher matcher = GREETING_LINE.matcher(greetingLine);
17+
if (!matcher.find()) {
18+
throw new IllegalArgumentException("Welcome message '" + greetingLine + "' is incorrect ");
19+
}
20+
serverVersion = matcher.group("version");
21+
protocolType = matcher.group("protocol");
22+
instanceUuid = matcher.group("uuid");
823
}
924

1025
public String getServerVersion() {
1126
return serverVersion;
1227
}
28+
29+
public String getProtocolType() {
30+
return protocolType;
31+
}
32+
33+
public String getInstanceUuid() {
34+
return instanceUuid;
35+
}
1336
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package org.tarantool.util;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* Server version holder.
7+
*/
8+
public class ServerVersion implements Comparable<ServerVersion> {
9+
10+
public static final ServerVersion V_1_9 = new ServerVersion(1, 9, 0);
11+
public static final ServerVersion V_1_10 = new ServerVersion(1, 10, 0);
12+
public static final ServerVersion V_2_1 = new ServerVersion(2, 1, 0);
13+
public static final ServerVersion V_2_2 = new ServerVersion(2, 2, 0);
14+
public static final ServerVersion V_2_2_1 = new ServerVersion(2, 2, 1);
15+
public static final ServerVersion V_2_3 = new ServerVersion(2, 3, 0);
16+
17+
private final int majorVersion;
18+
private final int minorVersion;
19+
private final int patchVersion;
20+
21+
/**
22+
* Makes a parsed server version container from
23+
* a string in format like {@code MAJOR.MINOR.PATCH[-BUILD-gCOMMIT]}.
24+
*
25+
* @param version string in the Tarantool version format.
26+
*/
27+
public ServerVersion(String version) {
28+
String[] parts = splitVersionParts(version);
29+
if (parts.length < 3) {
30+
throw new IllegalArgumentException("Expected at least major, minor, and patch version parts");
31+
}
32+
this.majorVersion = Integer.parseInt(parts[0]);
33+
this.minorVersion = Integer.parseInt(parts[1]);
34+
this.patchVersion = Integer.parseInt(parts[2]);
35+
}
36+
37+
public ServerVersion(int majorVersion,
38+
int minorVersion,
39+
int patchVersion) {
40+
this.majorVersion = majorVersion;
41+
this.minorVersion = minorVersion;
42+
this.patchVersion = patchVersion;
43+
}
44+
45+
public int getMajorVersion() {
46+
return majorVersion;
47+
}
48+
49+
public int getMinorVersion() {
50+
return minorVersion;
51+
}
52+
53+
public int getPatchVersion() {
54+
return patchVersion;
55+
}
56+
57+
public boolean isEqual(String versionString) {
58+
return isEqual(new ServerVersion(versionString));
59+
}
60+
61+
public boolean isEqual(ServerVersion version) {
62+
return compareTo(version) == 0;
63+
}
64+
65+
public boolean isLessOrEqualThan(String versionString) {
66+
return isLessOrEqualThan(new ServerVersion(versionString));
67+
}
68+
69+
public boolean isLessOrEqualThan(ServerVersion version) {
70+
return compareTo(version) <= 0;
71+
}
72+
73+
public boolean isGreaterOrEqualThan(String versionString) {
74+
return isGreaterOrEqualThan(new ServerVersion(versionString));
75+
}
76+
77+
public boolean isGreaterOrEqualThan(ServerVersion version) {
78+
return compareTo(version) >= 0;
79+
}
80+
81+
public boolean isGreaterThan(String versionString) {
82+
return isGreaterThan(new ServerVersion(versionString));
83+
}
84+
85+
public boolean isGreaterThan(ServerVersion version) {
86+
return compareTo(version) > 0;
87+
}
88+
89+
public boolean isLessThan(String versionString) {
90+
return isLessThan(new ServerVersion(versionString));
91+
}
92+
93+
public boolean isLessThan(ServerVersion version) {
94+
return compareTo(version) < 0;
95+
}
96+
97+
@Override
98+
public int compareTo(ServerVersion that) {
99+
return Integer.compare(this.toNumber(), that.toNumber());
100+
}
101+
102+
@Override
103+
public boolean equals(Object object) {
104+
if (this == object) {
105+
return true;
106+
}
107+
if (object == null || getClass() != object.getClass()) {
108+
return false;
109+
}
110+
ServerVersion that = (ServerVersion) object;
111+
return majorVersion == that.majorVersion &&
112+
minorVersion == that.minorVersion &&
113+
patchVersion == that.patchVersion;
114+
}
115+
116+
@Override
117+
public int hashCode() {
118+
return Objects.hash(majorVersion, minorVersion, patchVersion);
119+
}
120+
121+
/**
122+
* Translates version parts to format XXXYYYZZZ.
123+
* For example, {@code 1.2.3} translates to number {@code 1002003}
124+
*
125+
* @return version as number
126+
*/
127+
private int toNumber() {
128+
return (majorVersion * 1000 + minorVersion) * 1000 + patchVersion;
129+
}
130+
131+
/**
132+
* Splits Tarantool version string into parts.
133+
* For example, {@code 2.1.1-423-g4007436aa} => {@code [2, 1, 1, 423, g4007436aa]}.
134+
*
135+
* @param version Tarantool version string
136+
* @return split parts
137+
*/
138+
private String[] splitVersionParts(String version) {
139+
return version.split("[.\\-]");
140+
}
141+
}

src/test/java/org/tarantool/ClientOperationsIT.java

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import org.tarantool.schema.TarantoolIndexNotFoundException;
1010
import org.tarantool.schema.TarantoolSpaceNotFoundException;
11+
import org.tarantool.util.ServerVersion;
1112

1213
import org.junit.jupiter.api.AfterAll;
1314
import org.junit.jupiter.api.AfterEach;

0 commit comments

Comments
 (0)