Skip to content
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

fix(FIR-37280): parameter formatting for the new Firebolt #464

Merged
merged 14 commits into from
Oct 21, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,26 @@ void shouldInsertAndRetrieveUrl() throws SQLException, MalformedURLException {
}
}

@Test
void shouldFetchSpecialCharacters() throws SQLException, MalformedURLException {
try (Connection connection = createConnection()) {
try (PreparedStatement statement = connection
.prepareStatement("SELECT ? as a, ? as b, ? as c, ? as d")) {
statement.setString(1, "ї");
statement.setString(2, "\n");
statement.setString(3, "\\");
statement.setString(4, "don't");
statement.execute();
ResultSet rs = statement.getResultSet();
assertTrue(rs.next());
assertEquals("ї", rs.getString(1));
assertEquals("\n", rs.getString(2));
assertEquals("\\", rs.getString(3));
assertEquals("don't", rs.getString(4));
}
}
}

@Builder
@Value
@EqualsAndHashCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
import com.firebolt.jdbc.statement.FireboltStatement;
import com.firebolt.jdbc.statement.preparedstatement.FireboltPreparedStatement;
import com.firebolt.jdbc.type.FireboltDataType;
import com.firebolt.jdbc.type.ParserVersion;
import com.firebolt.jdbc.type.array.FireboltArray;
import com.firebolt.jdbc.type.lob.FireboltBlob;
import com.firebolt.jdbc.type.lob.FireboltClob;
import com.firebolt.jdbc.util.PropertyUtil;
import lombok.CustomLog;
import lombok.Getter;
import lombok.NonNull;
import okhttp3.OkHttpClient;

Expand Down Expand Up @@ -83,12 +85,16 @@ public abstract class FireboltConnection extends JdbcBase implements Connection,
//Properties that are used at the beginning of the connection for authentication
protected final FireboltProperties loginProperties;
private final Collection<CacheListener> cacheListeners = Collections.newSetFromMap(new IdentityHashMap<>());
// Parameter parser is determined by the version we're running on
@Getter
public final ParserVersion parserVersion;

protected FireboltConnection(@NonNull String url,
Properties connectionSettings,
FireboltAuthenticationService fireboltAuthenticationService,
FireboltStatementService fireboltStatementService,
String protocolVersion) {
String protocolVersion,
ParserVersion parserVersion) {
this.loginProperties = extractFireboltProperties(url, connectionSettings);

this.fireboltAuthenticationService = fireboltAuthenticationService;
Expand All @@ -99,11 +105,13 @@ protected FireboltConnection(@NonNull String url,
this.connectionTimeout = loginProperties.getConnectionTimeoutMillis();
this.networkTimeout = loginProperties.getSocketTimeoutMillis();
this.protocolVersion = protocolVersion;
this.parserVersion = parserVersion;
}

// This code duplication between constructors is done because of back reference: dependent services require reference to current instance of FireboltConnection that prevents using constructor chaining or factory method.
@ExcludeFromJacocoGeneratedReport
protected FireboltConnection(@NonNull String url, Properties connectionSettings, String protocolVersion) throws SQLException {
protected FireboltConnection(@NonNull String url, Properties connectionSettings, String protocolVersion,
ParserVersion parserVersion) throws SQLException {
this.loginProperties = extractFireboltProperties(url, connectionSettings);
OkHttpClient httpClient = getHttpClient(loginProperties);

Expand All @@ -115,6 +123,7 @@ protected FireboltConnection(@NonNull String url, Properties connectionSettings,
this.connectionTimeout = loginProperties.getConnectionTimeoutMillis();
this.networkTimeout = loginProperties.getSocketTimeoutMillis();
this.protocolVersion = protocolVersion;
this.parserVersion = parserVersion;
}

protected abstract FireboltAuthenticationClient createFireboltAuthenticationClient(OkHttpClient httpClient);
Expand All @@ -125,8 +134,10 @@ public static FireboltConnection create(@NonNull String url, Properties connecti

private static FireboltConnection createConnectionInstance(@NonNull String url, Properties connectionSettings) throws SQLException {
switch(getUrlVersion(url, connectionSettings)) {
case 1: return new FireboltConnectionUserPassword(url, connectionSettings);
case 2: return new FireboltConnectionServiceSecret(url, connectionSettings);
case 1:
return new FireboltConnectionUserPassword(url, connectionSettings, ParserVersion.LEGACY);
case 2:
return new FireboltConnectionServiceSecret(url, connectionSettings, ParserVersion.CURRENT);
default: throw new IllegalArgumentException(format("Cannot distinguish version from url %s", url));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.firebolt.jdbc.service.FireboltEngineVersion2Service;
import com.firebolt.jdbc.service.FireboltGatewayUrlService;
import com.firebolt.jdbc.service.FireboltStatementService;
import com.firebolt.jdbc.type.ParserVersion;
import com.firebolt.jdbc.util.PropertyUtil;
import lombok.NonNull;
import okhttp3.OkHttpClient;
Expand All @@ -42,16 +43,19 @@ public class FireboltConnectionServiceSecret extends FireboltConnection {
FireboltGatewayUrlService fireboltGatewayUrlService,
FireboltStatementService fireboltStatementService,
FireboltEngineInformationSchemaService fireboltEngineService,
FireboltAccountIdService fireboltAccountIdService) throws SQLException {
super(url, connectionSettings, fireboltAuthenticationService, fireboltStatementService, PROTOCOL_VERSION);
FireboltAccountIdService fireboltAccountIdService,
ParserVersion parserVersion) throws SQLException {
super(url, connectionSettings, fireboltAuthenticationService, fireboltStatementService, PROTOCOL_VERSION,
parserVersion);
this.fireboltGatewayUrlService = fireboltGatewayUrlService;
this.fireboltEngineService = fireboltEngineService;
connect();
}

@ExcludeFromJacocoGeneratedReport
FireboltConnectionServiceSecret(@NonNull String url, Properties connectionSettings) throws SQLException {
super(url, connectionSettings, PROTOCOL_VERSION);
FireboltConnectionServiceSecret(@NonNull String url, Properties connectionSettings, ParserVersion parserVersion)
throws SQLException {
super(url, connectionSettings, PROTOCOL_VERSION, parserVersion);
OkHttpClient httpClient = getHttpClient(loginProperties);
this.fireboltGatewayUrlService = new FireboltGatewayUrlService(createFireboltAccountRetriever(httpClient, GatewayUrlResponse.class));
connect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.firebolt.jdbc.service.FireboltEngineInformationSchemaService;
import com.firebolt.jdbc.service.FireboltEngineService;
import com.firebolt.jdbc.service.FireboltStatementService;
import com.firebolt.jdbc.type.ParserVersion;
import lombok.NonNull;
import okhttp3.OkHttpClient;

Expand All @@ -27,15 +28,18 @@ public class FireboltConnectionUserPassword extends FireboltConnection {
Properties connectionSettings,
FireboltAuthenticationService fireboltAuthenticationService,
FireboltStatementService fireboltStatementService,
FireboltEngineInformationSchemaService fireboltEngineService) throws SQLException {
super(url, connectionSettings, fireboltAuthenticationService, fireboltStatementService, PROTOCOL_VERSION);
FireboltEngineInformationSchemaService fireboltEngineService,
ParserVersion parserVersion) throws SQLException {
super(url, connectionSettings, fireboltAuthenticationService, fireboltStatementService, PROTOCOL_VERSION,
parserVersion);
this.fireboltEngineService = fireboltEngineService;
connect();
}

@ExcludeFromJacocoGeneratedReport
FireboltConnectionUserPassword(@NonNull String url, Properties connectionSettings) throws SQLException {
super(url, connectionSettings, PROTOCOL_VERSION);
FireboltConnectionUserPassword(@NonNull String url, Properties connectionSettings, ParserVersion parserVersion)
throws SQLException {
super(url, connectionSettings, PROTOCOL_VERSION, parserVersion);
OkHttpClient httpClient = getHttpClient(loginProperties);
this.fireboltEngineService = new FireboltEngineApiService(new FireboltAccountClient(httpClient, this, loginProperties.getUserDrivers(), loginProperties.getUserClients()));
connect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.firebolt.jdbc.statement.StatementInfoWrapper;
import com.firebolt.jdbc.statement.StatementUtil;
import com.firebolt.jdbc.statement.rawstatement.RawStatementWrapper;
import com.firebolt.jdbc.type.ParserVersion;
import com.firebolt.jdbc.type.JavaTypeToFireboltSQLString;
import com.firebolt.jdbc.util.InputStreamUtil;
import lombok.CustomLog;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class FireboltPreparedStatement extends FireboltStatement implements Prep
private final RawStatementWrapper rawStatement;
private final List<Map<Integer, String>> rows;
private Map<Integer, String> providedParameters;
private final ParserVersion parserVersion;

public FireboltPreparedStatement(FireboltStatementService statementService, FireboltConnection connection, String sql) {
this(statementService, connection.getSessionProperties(), connection, sql);
Expand All @@ -70,6 +72,7 @@ public FireboltPreparedStatement(FireboltStatementService statementService, Fire
this.rawStatement = StatementUtil.parseToRawStatementWrapper(sql);
rawStatement.getSubStatements().forEach(statement -> createValidator(statement, connection).validate(statement));
this.rows = new ArrayList<>();
this.parserVersion = connection.getParserVersion();
}

@Override
Expand Down Expand Up @@ -155,7 +158,7 @@ public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException
public void setString(int parameterIndex, String x) throws SQLException {
validateStatementIsNotClosed();
validateParamIndex(parameterIndex);
providedParameters.put(parameterIndex, JavaTypeToFireboltSQLString.STRING.transform(x));
providedParameters.put(parameterIndex, JavaTypeToFireboltSQLString.STRING.transform(x, parserVersion));
}

@Override
Expand Down Expand Up @@ -198,7 +201,8 @@ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQ
validateStatementIsNotClosed();
validateParamIndex(parameterIndex);
try {
providedParameters.put(parameterIndex, JavaTypeToFireboltSQLString.transformAny(x, targetSqlType));
providedParameters.put(parameterIndex,
JavaTypeToFireboltSQLString.transformAny(x, targetSqlType, parserVersion));
} catch (FireboltException fbe) {
if (ExceptionType.TYPE_NOT_SUPPORTED.equals(fbe.getType())) {
throw new SQLFeatureNotSupportedException(fbe.getMessage(), fbe);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/firebolt/jdbc/type/BaseType.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private static String checkInfinity(String s) {
}

public static boolean isNull(String value) {
return NULL_VALUE.equalsIgnoreCase(value);
return NULL_VALUE.equals(value);
}

private static boolean isNan(String value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public enum JavaTypeToFireboltSQLString {
UUID(java.util.UUID.class, Object::toString),
BYTE(Byte.class, value -> Byte.toString(((Number) value).byteValue())),
SHORT(Short.class, value -> Short.toString(((Number) value).shortValue())),
STRING(String.class, getSQLStringValueOfString()),
STRING(String.class, getSQLStringValueOfString(ParserVersion.CURRENT), getSQLStringValueOfStringVersioned()),
LONG(Long.class, value -> Long.toString(((Number)value).longValue())),
INTEGER(Integer.class, value -> Integer.toString(((Number)value).intValue())),
BIG_INTEGER(BigInteger.class, value -> value instanceof BigInteger ? value.toString() : Long.toString(((Number)value).longValue())),
Expand All @@ -47,8 +47,11 @@ public enum JavaTypeToFireboltSQLString {
ARRAY(Array.class, SqlArrayUtil::arrayToString),
BYTE_ARRAY(byte[].class, value -> ofNullable(byteArrayToHexString((byte[])value, true)).map(x -> format("E'%s'::BYTEA", x)).orElse(null)),
;
private static final List<Entry<String, String>> characterToEscapedCharacterPairs = List.of(

private static final List<Entry<String, String>> legacyCharacterToEscapedCharacterPairs = List.of(
Map.entry("\0", "\\0"), Map.entry("\\", "\\\\"), Map.entry("'", "''"));
private static final List<Entry<String, String>> characterToEscapedCharacterPairs = List.of(
Map.entry("'", "''"));
//https://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/mapping.html
private static final Map<JDBCType, Class<?>> jdbcTypeToClass = Map.ofEntries(
Map.entry(JDBCType.CHAR, String.class),
Expand Down Expand Up @@ -100,22 +103,33 @@ public enum JavaTypeToFireboltSQLString {
}

public static String transformAny(Object object) throws SQLException {
return transformAny(object, () -> getType(object));
return transformAny(object, ParserVersion.CURRENT);
}

public static String transformAny(Object object, ParserVersion version) throws SQLException {
return transformAny(object, () -> getType(object), version);
}

public static String transformAny(Object object, int sqlType) throws SQLException {
return transformAny(object, () -> getType(sqlType));
return transformAny(object, sqlType, ParserVersion.CURRENT);
}

private static String transformAny(Object object, CheckedSupplier<Class<?>> classSupplier) throws SQLException {
return object == null ? NULL_VALUE : transformAny(object, classSupplier.get());
public static String transformAny(Object object, int sqlType, ParserVersion version) throws SQLException {
return transformAny(object, () -> getType(sqlType), version);
}

private static String transformAny(Object object, Class<?> objectType) throws SQLException {
private static String transformAny(Object object, CheckedSupplier<Class<?>> classSupplier, ParserVersion version) throws SQLException {
return object == null ? NULL_VALUE : transformAny(object, classSupplier.get(), version);
}

private static String transformAny(Object object, Class<?> objectType, ParserVersion version) throws SQLException {
JavaTypeToFireboltSQLString converter = Optional.ofNullable(classToType.get(objectType))
.orElseThrow(() -> new FireboltException(
format("Cannot convert type %s. The type is not supported.", objectType),
TYPE_NOT_SUPPORTED));
if (version == ParserVersion.LEGACY && object instanceof String) {
return converter.transform(object, version);
}
return converter.transform(object);
}

Expand All @@ -133,10 +147,15 @@ private static Class<?> getType(int sqlType) throws SQLException {
}
}

private static CheckedFunction<Object, String> getSQLStringValueOfString() {
private static CheckedBiFunction<Object, Object, String> getSQLStringValueOfStringVersioned() {
return (value, version) -> getSQLStringValueOfString((ParserVersion) version).apply(value);
}

private static CheckedFunction<Object, String> getSQLStringValueOfString(ParserVersion version) {
return value -> {
String escaped = (String) value;
for (Entry<String, String> specialCharacter : characterToEscapedCharacterPairs) {
List<Entry<String, String>> charactersToEscape = version == ParserVersion.LEGACY ? legacyCharacterToEscapedCharacterPairs : characterToEscapedCharacterPairs;
for (Entry<String, String> specialCharacter : charactersToEscape) {
escaped = escaped.replace(specialCharacter.getKey(), specialCharacter.getValue());
}
return format("'%s'", escaped);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/firebolt/jdbc/type/ParserVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.firebolt.jdbc.type;

public enum ParserVersion {
LEGACY, CURRENT
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.firebolt.jdbc.exception.FireboltException;
import com.firebolt.jdbc.statement.StatementInfoWrapper;
import com.firebolt.jdbc.statement.StatementUtil;
import com.firebolt.jdbc.type.ParserVersion;
import lombok.NonNull;
import okhttp3.Call;
import okhttp3.Dispatcher;
Expand Down Expand Up @@ -349,7 +350,7 @@ private FireboltConnection use(int mockedInfraVersion, Properties props, String
Call useCall = getMockedCallWithResponse(200, "", responseHeaders);
Call select1Call = getMockedCallWithResponse(200, "");
when(okHttpClient.newCall(any())).thenReturn(useCall, select1Call);
FireboltConnection connection = new FireboltConnection("url", props, "0") {
FireboltConnection connection = new FireboltConnection("url", props, "0", ParserVersion.CURRENT) {
{
this.infraVersion = mockedInfraVersion;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.firebolt.jdbc.connection.settings.FireboltProperties;
import com.firebolt.jdbc.exception.FireboltException;
import com.firebolt.jdbc.service.FireboltGatewayUrlService;
import com.firebolt.jdbc.type.ParserVersion;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
Expand Down Expand Up @@ -100,7 +101,9 @@ void checkSystemEngineEndpoint(String gatewayUrl, String expectedHost, String ex
@SuppressWarnings("unchecked") FireboltAccountRetriever<GatewayUrlResponse> fireboltGatewayUrlClient = mock(FireboltAccountRetriever.class);
when(fireboltGatewayUrlClient.retrieve(any(), any())).thenReturn(new GatewayUrlResponse(gatewayUrl));
FireboltGatewayUrlService gatewayUrlService = new FireboltGatewayUrlService(fireboltGatewayUrlClient);
FireboltConnection connection = new FireboltConnectionServiceSecret(SYSTEM_ENGINE_URL, connectionProperties, fireboltAuthenticationService, gatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService);
FireboltConnection connection = new FireboltConnectionServiceSecret(SYSTEM_ENGINE_URL, connectionProperties,
fireboltAuthenticationService, gatewayUrlService, fireboltStatementService, fireboltEngineService,
fireboltAccountIdService, ParserVersion.CURRENT);
FireboltProperties sessionProperties = connection.getSessionProperties();
assertEquals(expectedHost, sessionProperties.getHost());
assertEquals(expectedProps == null ? Map.of() : Arrays.stream(expectedProps.split(";")).map(kv -> kv.split("=")).collect(toMap(kv -> kv[0], kv -> kv[1])), sessionProperties.getAdditionalProperties());
Expand All @@ -113,6 +116,7 @@ void shouldNotFetchTokenNorEngineHostForLocalFirebolt() throws SQLException {
}

protected FireboltConnection createConnection(String url, Properties props) throws SQLException {
return new FireboltConnectionServiceSecret(url, props, fireboltAuthenticationService, fireboltGatewayUrlService, fireboltStatementService, fireboltEngineService, fireboltAccountIdService);
return new FireboltConnectionServiceSecret(url, props, fireboltAuthenticationService, fireboltGatewayUrlService,
fireboltStatementService, fireboltEngineService, fireboltAccountIdService, ParserVersion.CURRENT);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.firebolt.jdbc.connection;

import com.firebolt.jdbc.type.ParserVersion;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
Expand Down Expand Up @@ -73,6 +74,7 @@ void getMetadata(String engine) throws SQLException {
}

protected FireboltConnection createConnection(String url, Properties props) throws SQLException {
return new FireboltConnectionUserPassword(url, props, fireboltAuthenticationService, fireboltStatementService, fireboltEngineService);
return new FireboltConnectionUserPassword(url, props, fireboltAuthenticationService, fireboltStatementService,
fireboltEngineService, ParserVersion.LEGACY);
}
}
Loading
Loading