Skip to content

Commit

Permalink
feat: support default ClientInfo properties (#324)
Browse files Browse the repository at this point in the history
  • Loading branch information
olavloite committed Jan 17, 2021
1 parent f8149af commit 250c4c1
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 22 deletions.
Expand Up @@ -45,19 +45,21 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
private static final String ABORT_UNSUPPORTED = "Abort is not supported";
private static final String NETWORK_TIMEOUT_UNSUPPORTED = "Network timeout is not supported";
static final String CLIENT_INFO_NOT_SUPPORTED =
"Cloud Spanner does not support any ClientInfo properties";
"Cloud Spanner does not support ClientInfo property %s";

private final String connectionUrl;
private final ConnectionOptions options;
private final com.google.cloud.spanner.connection.Connection spanner;
private final Properties clientInfo;

private SQLWarning firstWarning = null;
private SQLWarning lastWarning = null;

AbstractJdbcConnection(String connectionUrl, ConnectionOptions options) {
AbstractJdbcConnection(String connectionUrl, ConnectionOptions options) throws SQLException {
this.connectionUrl = connectionUrl;
this.options = options;
this.spanner = options.getConnection();
this.clientInfo = new Properties(JdbcDatabaseMetaData.getDefaultClientInfoProperties());
}

/** Return the corresponding {@link com.google.cloud.spanner.connection.Connection} */
Expand Down Expand Up @@ -168,8 +170,10 @@ public SQLXML createSQLXML() throws SQLException {

@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
Properties supported = null;
try {
checkClosed();
supported = JdbcDatabaseMetaData.getDefaultClientInfoProperties();
} catch (SQLException e) {
if (e instanceof JdbcSqlException) {
throw JdbcSqlExceptionFactory.clientInfoException(
Expand All @@ -178,7 +182,23 @@ public void setClientInfo(String name, String value) throws SQLClientInfoExcepti
throw JdbcSqlExceptionFactory.clientInfoException(e.getMessage(), Code.UNKNOWN);
}
}
pushWarning(new SQLWarning(CLIENT_INFO_NOT_SUPPORTED));
if (value == null) {
throw JdbcSqlExceptionFactory.clientInfoException(
"Null-value is not allowed for client info.", Code.INVALID_ARGUMENT);
}
if (value.length() > JdbcDatabaseMetaData.MAX_CLIENT_INFO_VALUE_LENGTH) {
throw JdbcSqlExceptionFactory.clientInfoException(
String.format(
"Max length of value is %d characters.",
JdbcDatabaseMetaData.MAX_CLIENT_INFO_VALUE_LENGTH),
Code.INVALID_ARGUMENT);
}
name = name.toUpperCase();
if (supported.containsKey(name)) {
clientInfo.setProperty(name, value);
} else {
pushWarning(new SQLWarning(String.format(CLIENT_INFO_NOT_SUPPORTED, name)));
}
}

@Override
Expand All @@ -193,19 +213,22 @@ public void setClientInfo(Properties properties) throws SQLClientInfoException {
throw JdbcSqlExceptionFactory.clientInfoException(e.getMessage(), Code.UNKNOWN);
}
}
pushWarning(new SQLWarning(CLIENT_INFO_NOT_SUPPORTED));
clientInfo.clear();
for (String property : properties.stringPropertyNames()) {
setClientInfo(property, properties.getProperty(property));
}
}

@Override
public String getClientInfo(String name) throws SQLException {
checkClosed();
return null;
return clientInfo.getProperty(name.toUpperCase());
}

@Override
public Properties getClientInfo() throws SQLException {
checkClosed();
return null;
return (Properties) clientInfo.clone();
}

@Override
Expand Down
Expand Up @@ -53,7 +53,7 @@ class JdbcConnection extends AbstractJdbcConnection {

private Map<String, Class<?>> typeMap = new HashMap<>();

JdbcConnection(String connectionUrl, ConnectionOptions options) {
JdbcConnection(String connectionUrl, ConnectionOptions options) throws SQLException {
super(connectionUrl, options);
}

Expand Down
Expand Up @@ -36,6 +36,7 @@
import java.sql.Types;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
import java.util.Scanner;

/** {@link DatabaseMetaData} implementation for Cloud Spanner */
Expand Down Expand Up @@ -1495,16 +1496,68 @@ public boolean autoCommitFailureClosesAllResultSets() throws SQLException {
return false;
}

@Override
public ResultSet getClientInfoProperties() throws SQLException {
/**
* The max length for client info values is 63 to make them fit in Cloud Spanner session labels.
*/
static final int MAX_CLIENT_INFO_VALUE_LENGTH = 63;

static Properties getDefaultClientInfoProperties() throws SQLException {
Properties info = new Properties();
try (ResultSet rs = getDefaultClientInfo()) {
while (rs.next()) {
info.put(rs.getString("NAME"), rs.getString("DEFAULT_VALUE"));
}
}
return info;
}

private static ResultSet getDefaultClientInfo() throws SQLException {
return JdbcResultSet.of(
ResultSets.forRows(
Type.struct(
StructField.of("NAME", Type.string()),
StructField.of("MAX_LEN", Type.string()),
StructField.of("MAX_LEN", Type.int64()),
StructField.of("DEFAULT_VALUE", Type.string()),
StructField.of("DESCRIPTION", Type.string())),
Collections.<Struct>emptyList()));
Arrays.asList(
Struct.newBuilder()
.set("NAME")
.to("APPLICATIONNAME")
.set("MAX_LEN")
.to(MAX_CLIENT_INFO_VALUE_LENGTH)
.set("DEFAULT_VALUE")
.to("")
.set("DESCRIPTION")
.to("The name of the application currently utilizing the connection.")
.build(),
Struct.newBuilder()
.set("NAME")
.to("CLIENTHOSTNAME")
.set("MAX_LEN")
.to(MAX_CLIENT_INFO_VALUE_LENGTH)
.set("DEFAULT_VALUE")
.to("")
.set("DESCRIPTION")
.to(
"The hostname of the computer the application using the connection is running on.")
.build(),
Struct.newBuilder()
.set("NAME")
.to("CLIENTUSER")
.set("MAX_LEN")
.to(MAX_CLIENT_INFO_VALUE_LENGTH)
.set("DEFAULT_VALUE")
.to("")
.set("DESCRIPTION")
.to(
"The name of the user that the application using the connection is performing work for. "
+ "This may not be the same as the user name that was used in establishing the connection.")
.build())));
}

@Override
public ResultSet getClientInfoProperties() throws SQLException {
return getDefaultClientInfo();
}

@Override
Expand Down
Expand Up @@ -19,12 +19,14 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.connection.AbstractSqlScriptVerifier.GenericConnection;
import com.google.cloud.spanner.connection.AbstractSqlScriptVerifier.GenericConnectionProvider;
import com.google.cloud.spanner.connection.ConnectionImplTest;
import com.google.cloud.spanner.connection.ConnectionOptions;
import com.google.cloud.spanner.connection.SqlScriptVerifier;
import com.google.cloud.spanner.jdbc.JdbcSqlScriptVerifier.JdbcGenericConnection;
import java.sql.SQLException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
Expand All @@ -46,13 +48,17 @@ public GenericConnection getConnection() {
com.google.cloud.spanner.connection.Connection spannerConnection =
ConnectionImplTest.createConnection(options);
when(options.getConnection()).thenReturn(spannerConnection);
JdbcConnection connection =
new JdbcConnection(
"jdbc:cloudspanner://localhost/projects/project/instances/instance/databases/database;credentialsUrl=url",
options);
JdbcGenericConnection res = JdbcGenericConnection.of(connection);
res.setStripCommentsBeforeExecute(true);
return res;
try {
JdbcConnection connection =
new JdbcConnection(
"jdbc:cloudspanner://localhost/projects/project/instances/instance/databases/database;credentialsUrl=url",
options);
JdbcGenericConnection res = JdbcGenericConnection.of(connection);
res.setStripCommentsBeforeExecute(true);
return res;
} catch (SQLException e) {
throw SpannerExceptionFactory.asSpannerException(e);
}
}
}

Expand Down
Expand Up @@ -59,7 +59,7 @@ public class JdbcConnectionTest {
Type.struct(StructField.of("", Type.int64())),
Arrays.asList(Struct.newBuilder().set("").to(1L).build()));

private JdbcConnection createConnection(ConnectionOptions options) {
private JdbcConnection createConnection(ConnectionOptions options) throws SQLException {
com.google.cloud.spanner.connection.Connection spannerConnection =
ConnectionImplTest.createConnection(options);
when(options.getConnection()).thenReturn(spannerConnection);
Expand Down Expand Up @@ -405,14 +405,24 @@ public void testWarnings() throws SQLException {
}

@Test
public void testSetClientInfo() throws SQLException {
public void getDefaultClientInfo() throws SQLException {
ConnectionOptions options = mock(ConnectionOptions.class);
try (JdbcConnection connection = createConnection(options)) {
Properties defaultProperties = connection.getClientInfo();
assertThat(defaultProperties.stringPropertyNames())
.containsExactly("APPLICATIONNAME", "CLIENTHOSTNAME", "CLIENTUSER");
}
}

@Test
public void testSetInvalidClientInfo() throws SQLException {
ConnectionOptions options = mock(ConnectionOptions.class);
try (JdbcConnection connection = createConnection(options)) {
assertThat((Object) connection.getWarnings()).isNull();
connection.setClientInfo("test", "foo");
assertThat((Object) connection.getWarnings()).isNotNull();
assertThat(connection.getWarnings().getMessage())
.isEqualTo(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED);
.isEqualTo(String.format(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED, "TEST"));

connection.clearWarnings();
assertThat((Object) connection.getWarnings()).isNull();
Expand All @@ -422,7 +432,38 @@ public void testSetClientInfo() throws SQLException {
connection.setClientInfo(props);
assertThat((Object) connection.getWarnings()).isNotNull();
assertThat(connection.getWarnings().getMessage())
.isEqualTo(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED);
.isEqualTo(String.format(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED, "TEST"));
}
}

@Test
public void testSetClientInfo() throws SQLException {
ConnectionOptions options = mock(ConnectionOptions.class);
try (JdbcConnection connection = createConnection(options)) {
try (ResultSet validProperties = connection.getMetaData().getClientInfoProperties()) {
while (validProperties.next()) {
assertThat((Object) connection.getWarnings()).isNull();
String name = validProperties.getString("NAME");

connection.setClientInfo(name, "new-client-info-value");
assertThat((Object) connection.getWarnings()).isNull();
assertThat(connection.getClientInfo(name)).isEqualTo("new-client-info-value");

Properties props = new Properties();
props.setProperty(name.toLowerCase(), "some-other-value");
connection.setClientInfo(props);
assertThat((Object) connection.getWarnings()).isNull();
assertThat(connection.getClientInfo(name)).isEqualTo("some-other-value");
assertThat(connection.getClientInfo().keySet()).hasSize(1);
for (String key : connection.getClientInfo().stringPropertyNames()) {
if (key.equals(name)) {
assertThat(connection.getClientInfo().getProperty(key)).isEqualTo("some-other-value");
} else {
assertThat(connection.getClientInfo().getProperty(key)).isEqualTo("");
}
}
}
}
}
}

Expand Down
Expand Up @@ -322,6 +322,18 @@ public void testGetClientInfoProperties() throws SQLException {
JdbcConnection connection = mock(JdbcConnection.class);
DatabaseMetaData meta = new JdbcDatabaseMetaData(connection);
try (ResultSet rs = meta.getClientInfoProperties()) {
assertThat(rs.next(), is(true));
assertThat(rs.getString("NAME"), is(equalTo("APPLICATIONNAME")));
assertThat(rs.getString("DEFAULT_VALUE"), is(equalTo("")));

assertThat(rs.next(), is(true));
assertThat(rs.getString("NAME"), is(equalTo("CLIENTHOSTNAME")));
assertThat(rs.getString("DEFAULT_VALUE"), is(equalTo("")));

assertThat(rs.next(), is(true));
assertThat(rs.getString("NAME"), is(equalTo("CLIENTUSER")));
assertThat(rs.getString("DEFAULT_VALUE"), is(equalTo("")));

assertThat(rs.next(), is(false));
ResultSetMetaData rsmd = rs.getMetaData();
assertThat(rsmd.getColumnCount(), is(equalTo(4)));
Expand Down

0 comments on commit 250c4c1

Please sign in to comment.