Skip to content

Support JDBC escape syntax #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
416 changes: 416 additions & 0 deletions src/main/java/org/tarantool/jdbc/EscapeSyntaxParser.java

Large diffs are not rendered by default.

444 changes: 444 additions & 0 deletions src/main/java/org/tarantool/jdbc/EscapedFunctions.java

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/main/java/org/tarantool/jdbc/SQLConnection.java
Original file line number Diff line number Diff line change
@@ -63,12 +63,15 @@ public class SQLConnection implements TarantoolConnection {
private DatabaseMetaData cachedMetadata;
private int resultSetHoldability = UNSET_HOLDABILITY;

private final EscapeSyntaxParser escapeSyntaxParser;

public SQLConnection(String url, Properties properties) throws SQLException {
this.url = url;
this.properties = properties;

try {
client = makeSqlClient(makeAddress(properties), makeConfigFromProperties(properties));
escapeSyntaxParser = new EscapeSyntaxParser(this);
} catch (Exception e) {
throw new SQLException("Couldn't initiate connection using " + SQLDriver.diagProperties(properties), e);
}
@@ -189,7 +192,7 @@ public CallableStatement prepareCall(String sql,
@Override
public String nativeSQL(String sql) throws SQLException {
checkNotClosed();
throw new SQLFeatureNotSupportedException();
return escapeSyntaxParser.translate(sql, true);
}

@Override
3 changes: 3 additions & 0 deletions src/main/java/org/tarantool/jdbc/SQLConstant.java
Original file line number Diff line number Diff line change
@@ -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;

}
32 changes: 21 additions & 11 deletions src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java
Original file line number Diff line number Diff line change
@@ -5,10 +5,11 @@
import org.tarantool.SqlProtoUtils;
import org.tarantool.Version;
import org.tarantool.jdbc.type.TarantoolSqlType;
import org.tarantool.util.ServerVersion;
import org.tarantool.util.StringUtils;
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;
@@ -21,7 +22,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;
@@ -89,7 +90,7 @@ public boolean nullsAreSortedAtEnd() throws SQLException {

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

@Override
@@ -179,17 +180,17 @@ public String getSQLKeywords() throws SQLException {

@Override
public String getNumericFunctions() throws SQLException {
return "";
return StringUtils.toCsvList(EscapedFunctions.NumericFunction.values());
}

@Override
public String getStringFunctions() throws SQLException {
return "";
return StringUtils.toCsvList(EscapedFunctions.StringFunction.values());
}

@Override
public String getSystemFunctions() throws SQLException {
return "";
return StringUtils.toCsvList(EscapedFunctions.SystemFunction.values());
}

@Override
@@ -274,7 +275,7 @@ public boolean supportsGroupByBeyondSelect() throws SQLException {

@Override
public boolean supportsLikeEscapeClause() throws SQLException {
return false;
return true;
}

@Override
@@ -1017,22 +1018,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
4 changes: 2 additions & 2 deletions src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ public class SQLPreparedStatement extends SQLStatement implements PreparedStatem

public SQLPreparedStatement(SQLConnection connection, String sql, int autoGeneratedKeys) throws SQLException {
super(connection);
this.sql = sql;
this.sql = translateQuery(sql);
this.parameters = new HashMap<>();
this.autoGeneratedKeys = autoGeneratedKeys;
setPoolable(true);
@@ -55,7 +55,7 @@ public SQLPreparedStatement(SQLConnection connection,
int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
super(connection, resultSetType, resultSetConcurrency, resultSetHoldability);
this.sql = sql;
this.sql = translateQuery(sql);
this.parameters = new HashMap<>();
this.autoGeneratedKeys = NO_GENERATED_KEYS;
setPoolable(true);
14 changes: 10 additions & 4 deletions src/main/java/org/tarantool/jdbc/SQLStatement.java
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ public class SQLStatement implements TarantoolStatement {
private List<String> batchQueries = new ArrayList<>();

private boolean isCloseOnCompletion;
private boolean useEscapeProcessing = true;

private final int resultSetType;
private final int resultSetConcurrency;
@@ -91,7 +92,7 @@ protected SQLStatement(SQLConnection sqlConnection,
@Override
public ResultSet executeQuery(String sql) throws SQLException {
checkNotClosed();
if (!executeInternal(NO_GENERATED_KEYS, sql)) {
if (!executeInternal(NO_GENERATED_KEYS, translateQuery(sql))) {
throw new SQLException("No results were returned", SQLStates.NO_DATA.getSqlState());
}
return resultSet;
@@ -106,7 +107,7 @@ public int executeUpdate(String sql) throws SQLException {
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
checkNotClosed();
JdbcConstants.checkGeneratedKeysConstant(autoGeneratedKeys);
if (executeInternal(autoGeneratedKeys, sql)) {
if (executeInternal(autoGeneratedKeys, translateQuery(sql))) {
throw new SQLException(
"Result was returned but nothing was expected",
SQLStates.TOO_MANY_RESULTS.getSqlState()
@@ -166,7 +167,8 @@ public void setMaxRows(int maxRows) throws SQLException {

@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkNotClosed();
useEscapeProcessing = enable;
}

@Override
@@ -208,7 +210,7 @@ public void setCursorName(String name) throws SQLException {
@Override
public boolean execute(String sql) throws SQLException {
checkNotClosed();
return executeInternal(NO_GENERATED_KEYS, sql);
return executeInternal(NO_GENERATED_KEYS, translateQuery(sql));
}

@Override
@@ -511,4 +513,8 @@ protected SQLResultSet executeGeneratedKeys(List<Integer> generatedKeys) throws
return createResultSet(SQLResultHolder.ofQuery(Collections.singletonList(sqlMetaData), rows));
}

protected String translateQuery(String sql) throws SQLException {
return useEscapeProcessing ? connection.nativeSQL(sql) : sql;
}

}
20 changes: 20 additions & 0 deletions src/main/java/org/tarantool/jdbc/TarantoolDatabaseMetaData.java
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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}.
@@ -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}
*
@@ -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/
@@ -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());
final TarantoolGreeting greeting = parseGreetingLine(firstLine, socket.getRemoteSocketAddress());

inputStream.read(inputBytes);
String salt = new String(inputBytes);
@@ -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/
@@ -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());
final TarantoolGreeting greeting = parseGreetingLine(firstLine, channel.getRemoteAddress());

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

@@ -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) {
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
}
}
3 changes: 2 additions & 1 deletion src/main/java/org/tarantool/util/SQLStates.java
Original file line number Diff line number Diff line change
@@ -7,7 +7,8 @@ public enum SQLStates {
CONNECTION_DOES_NOT_EXIST("08003"),
INVALID_PARAMETER_VALUE("22023"),
INVALID_CURSOR_STATE("24000"),
INVALID_TRANSACTION_STATE("25000");
INVALID_TRANSACTION_STATE("25000"),
SYNTAX_ERROR("42000");

private final String sqlState;

Loading