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

feat: support default ClientInfo properties #324

Merged
merged 1 commit into from Jan 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These default ClientInfo properties are described here: https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#setClientInfo-java.lang.String-java.lang.String-

The names are case insensitive.

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