Skip to content

Commit 250c4c1

Browse files
authored
feat: support default ClientInfo properties (googleapis#324)
1 parent f8149af commit 250c4c1

File tree

6 files changed

+157
-22
lines changed

6 files changed

+157
-22
lines changed

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcConnection.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,21 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
4545
private static final String ABORT_UNSUPPORTED = "Abort is not supported";
4646
private static final String NETWORK_TIMEOUT_UNSUPPORTED = "Network timeout is not supported";
4747
static final String CLIENT_INFO_NOT_SUPPORTED =
48-
"Cloud Spanner does not support any ClientInfo properties";
48+
"Cloud Spanner does not support ClientInfo property %s";
4949

5050
private final String connectionUrl;
5151
private final ConnectionOptions options;
5252
private final com.google.cloud.spanner.connection.Connection spanner;
53+
private final Properties clientInfo;
5354

5455
private SQLWarning firstWarning = null;
5556
private SQLWarning lastWarning = null;
5657

57-
AbstractJdbcConnection(String connectionUrl, ConnectionOptions options) {
58+
AbstractJdbcConnection(String connectionUrl, ConnectionOptions options) throws SQLException {
5859
this.connectionUrl = connectionUrl;
5960
this.options = options;
6061
this.spanner = options.getConnection();
62+
this.clientInfo = new Properties(JdbcDatabaseMetaData.getDefaultClientInfoProperties());
6163
}
6264

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

169171
@Override
170172
public void setClientInfo(String name, String value) throws SQLClientInfoException {
173+
Properties supported = null;
171174
try {
172175
checkClosed();
176+
supported = JdbcDatabaseMetaData.getDefaultClientInfoProperties();
173177
} catch (SQLException e) {
174178
if (e instanceof JdbcSqlException) {
175179
throw JdbcSqlExceptionFactory.clientInfoException(
@@ -178,7 +182,23 @@ public void setClientInfo(String name, String value) throws SQLClientInfoExcepti
178182
throw JdbcSqlExceptionFactory.clientInfoException(e.getMessage(), Code.UNKNOWN);
179183
}
180184
}
181-
pushWarning(new SQLWarning(CLIENT_INFO_NOT_SUPPORTED));
185+
if (value == null) {
186+
throw JdbcSqlExceptionFactory.clientInfoException(
187+
"Null-value is not allowed for client info.", Code.INVALID_ARGUMENT);
188+
}
189+
if (value.length() > JdbcDatabaseMetaData.MAX_CLIENT_INFO_VALUE_LENGTH) {
190+
throw JdbcSqlExceptionFactory.clientInfoException(
191+
String.format(
192+
"Max length of value is %d characters.",
193+
JdbcDatabaseMetaData.MAX_CLIENT_INFO_VALUE_LENGTH),
194+
Code.INVALID_ARGUMENT);
195+
}
196+
name = name.toUpperCase();
197+
if (supported.containsKey(name)) {
198+
clientInfo.setProperty(name, value);
199+
} else {
200+
pushWarning(new SQLWarning(String.format(CLIENT_INFO_NOT_SUPPORTED, name)));
201+
}
182202
}
183203

184204
@Override
@@ -193,19 +213,22 @@ public void setClientInfo(Properties properties) throws SQLClientInfoException {
193213
throw JdbcSqlExceptionFactory.clientInfoException(e.getMessage(), Code.UNKNOWN);
194214
}
195215
}
196-
pushWarning(new SQLWarning(CLIENT_INFO_NOT_SUPPORTED));
216+
clientInfo.clear();
217+
for (String property : properties.stringPropertyNames()) {
218+
setClientInfo(property, properties.getProperty(property));
219+
}
197220
}
198221

199222
@Override
200223
public String getClientInfo(String name) throws SQLException {
201224
checkClosed();
202-
return null;
225+
return clientInfo.getProperty(name.toUpperCase());
203226
}
204227

205228
@Override
206229
public Properties getClientInfo() throws SQLException {
207230
checkClosed();
208-
return null;
231+
return (Properties) clientInfo.clone();
209232
}
210233

211234
@Override

src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class JdbcConnection extends AbstractJdbcConnection {
5353

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

56-
JdbcConnection(String connectionUrl, ConnectionOptions options) {
56+
JdbcConnection(String connectionUrl, ConnectionOptions options) throws SQLException {
5757
super(connectionUrl, options);
5858
}
5959

src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.sql.Types;
3737
import java.util.Arrays;
3838
import java.util.Collections;
39+
import java.util.Properties;
3940
import java.util.Scanner;
4041

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

1498-
@Override
1499-
public ResultSet getClientInfoProperties() throws SQLException {
1499+
/**
1500+
* The max length for client info values is 63 to make them fit in Cloud Spanner session labels.
1501+
*/
1502+
static final int MAX_CLIENT_INFO_VALUE_LENGTH = 63;
1503+
1504+
static Properties getDefaultClientInfoProperties() throws SQLException {
1505+
Properties info = new Properties();
1506+
try (ResultSet rs = getDefaultClientInfo()) {
1507+
while (rs.next()) {
1508+
info.put(rs.getString("NAME"), rs.getString("DEFAULT_VALUE"));
1509+
}
1510+
}
1511+
return info;
1512+
}
1513+
1514+
private static ResultSet getDefaultClientInfo() throws SQLException {
15001515
return JdbcResultSet.of(
15011516
ResultSets.forRows(
15021517
Type.struct(
15031518
StructField.of("NAME", Type.string()),
1504-
StructField.of("MAX_LEN", Type.string()),
1519+
StructField.of("MAX_LEN", Type.int64()),
15051520
StructField.of("DEFAULT_VALUE", Type.string()),
15061521
StructField.of("DESCRIPTION", Type.string())),
1507-
Collections.<Struct>emptyList()));
1522+
Arrays.asList(
1523+
Struct.newBuilder()
1524+
.set("NAME")
1525+
.to("APPLICATIONNAME")
1526+
.set("MAX_LEN")
1527+
.to(MAX_CLIENT_INFO_VALUE_LENGTH)
1528+
.set("DEFAULT_VALUE")
1529+
.to("")
1530+
.set("DESCRIPTION")
1531+
.to("The name of the application currently utilizing the connection.")
1532+
.build(),
1533+
Struct.newBuilder()
1534+
.set("NAME")
1535+
.to("CLIENTHOSTNAME")
1536+
.set("MAX_LEN")
1537+
.to(MAX_CLIENT_INFO_VALUE_LENGTH)
1538+
.set("DEFAULT_VALUE")
1539+
.to("")
1540+
.set("DESCRIPTION")
1541+
.to(
1542+
"The hostname of the computer the application using the connection is running on.")
1543+
.build(),
1544+
Struct.newBuilder()
1545+
.set("NAME")
1546+
.to("CLIENTUSER")
1547+
.set("MAX_LEN")
1548+
.to(MAX_CLIENT_INFO_VALUE_LENGTH)
1549+
.set("DEFAULT_VALUE")
1550+
.to("")
1551+
.set("DESCRIPTION")
1552+
.to(
1553+
"The name of the user that the application using the connection is performing work for. "
1554+
+ "This may not be the same as the user name that was used in establishing the connection.")
1555+
.build())));
1556+
}
1557+
1558+
@Override
1559+
public ResultSet getClientInfoProperties() throws SQLException {
1560+
return getDefaultClientInfo();
15081561
}
15091562

15101563
@Override

src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionGeneratedSqlScriptTest.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
import static org.mockito.Mockito.mock;
2020
import static org.mockito.Mockito.when;
2121

22+
import com.google.cloud.spanner.SpannerExceptionFactory;
2223
import com.google.cloud.spanner.connection.AbstractSqlScriptVerifier.GenericConnection;
2324
import com.google.cloud.spanner.connection.AbstractSqlScriptVerifier.GenericConnectionProvider;
2425
import com.google.cloud.spanner.connection.ConnectionImplTest;
2526
import com.google.cloud.spanner.connection.ConnectionOptions;
2627
import com.google.cloud.spanner.connection.SqlScriptVerifier;
2728
import com.google.cloud.spanner.jdbc.JdbcSqlScriptVerifier.JdbcGenericConnection;
29+
import java.sql.SQLException;
2830
import org.junit.Test;
2931
import org.junit.runner.RunWith;
3032
import org.junit.runners.JUnit4;
@@ -46,13 +48,17 @@ public GenericConnection getConnection() {
4648
com.google.cloud.spanner.connection.Connection spannerConnection =
4749
ConnectionImplTest.createConnection(options);
4850
when(options.getConnection()).thenReturn(spannerConnection);
49-
JdbcConnection connection =
50-
new JdbcConnection(
51-
"jdbc:cloudspanner://localhost/projects/project/instances/instance/databases/database;credentialsUrl=url",
52-
options);
53-
JdbcGenericConnection res = JdbcGenericConnection.of(connection);
54-
res.setStripCommentsBeforeExecute(true);
55-
return res;
51+
try {
52+
JdbcConnection connection =
53+
new JdbcConnection(
54+
"jdbc:cloudspanner://localhost/projects/project/instances/instance/databases/database;credentialsUrl=url",
55+
options);
56+
JdbcGenericConnection res = JdbcGenericConnection.of(connection);
57+
res.setStripCommentsBeforeExecute(true);
58+
return res;
59+
} catch (SQLException e) {
60+
throw SpannerExceptionFactory.asSpannerException(e);
61+
}
5662
}
5763
}
5864

src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionTest.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public class JdbcConnectionTest {
5959
Type.struct(StructField.of("", Type.int64())),
6060
Arrays.asList(Struct.newBuilder().set("").to(1L).build()));
6161

62-
private JdbcConnection createConnection(ConnectionOptions options) {
62+
private JdbcConnection createConnection(ConnectionOptions options) throws SQLException {
6363
com.google.cloud.spanner.connection.Connection spannerConnection =
6464
ConnectionImplTest.createConnection(options);
6565
when(options.getConnection()).thenReturn(spannerConnection);
@@ -405,14 +405,24 @@ public void testWarnings() throws SQLException {
405405
}
406406

407407
@Test
408-
public void testSetClientInfo() throws SQLException {
408+
public void getDefaultClientInfo() throws SQLException {
409+
ConnectionOptions options = mock(ConnectionOptions.class);
410+
try (JdbcConnection connection = createConnection(options)) {
411+
Properties defaultProperties = connection.getClientInfo();
412+
assertThat(defaultProperties.stringPropertyNames())
413+
.containsExactly("APPLICATIONNAME", "CLIENTHOSTNAME", "CLIENTUSER");
414+
}
415+
}
416+
417+
@Test
418+
public void testSetInvalidClientInfo() throws SQLException {
409419
ConnectionOptions options = mock(ConnectionOptions.class);
410420
try (JdbcConnection connection = createConnection(options)) {
411421
assertThat((Object) connection.getWarnings()).isNull();
412422
connection.setClientInfo("test", "foo");
413423
assertThat((Object) connection.getWarnings()).isNotNull();
414424
assertThat(connection.getWarnings().getMessage())
415-
.isEqualTo(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED);
425+
.isEqualTo(String.format(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED, "TEST"));
416426

417427
connection.clearWarnings();
418428
assertThat((Object) connection.getWarnings()).isNull();
@@ -422,7 +432,38 @@ public void testSetClientInfo() throws SQLException {
422432
connection.setClientInfo(props);
423433
assertThat((Object) connection.getWarnings()).isNotNull();
424434
assertThat(connection.getWarnings().getMessage())
425-
.isEqualTo(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED);
435+
.isEqualTo(String.format(AbstractJdbcConnection.CLIENT_INFO_NOT_SUPPORTED, "TEST"));
436+
}
437+
}
438+
439+
@Test
440+
public void testSetClientInfo() throws SQLException {
441+
ConnectionOptions options = mock(ConnectionOptions.class);
442+
try (JdbcConnection connection = createConnection(options)) {
443+
try (ResultSet validProperties = connection.getMetaData().getClientInfoProperties()) {
444+
while (validProperties.next()) {
445+
assertThat((Object) connection.getWarnings()).isNull();
446+
String name = validProperties.getString("NAME");
447+
448+
connection.setClientInfo(name, "new-client-info-value");
449+
assertThat((Object) connection.getWarnings()).isNull();
450+
assertThat(connection.getClientInfo(name)).isEqualTo("new-client-info-value");
451+
452+
Properties props = new Properties();
453+
props.setProperty(name.toLowerCase(), "some-other-value");
454+
connection.setClientInfo(props);
455+
assertThat((Object) connection.getWarnings()).isNull();
456+
assertThat(connection.getClientInfo(name)).isEqualTo("some-other-value");
457+
assertThat(connection.getClientInfo().keySet()).hasSize(1);
458+
for (String key : connection.getClientInfo().stringPropertyNames()) {
459+
if (key.equals(name)) {
460+
assertThat(connection.getClientInfo().getProperty(key)).isEqualTo("some-other-value");
461+
} else {
462+
assertThat(connection.getClientInfo().getProperty(key)).isEqualTo("");
463+
}
464+
}
465+
}
466+
}
426467
}
427468
}
428469

src/test/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaDataTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,18 @@ public void testGetClientInfoProperties() throws SQLException {
322322
JdbcConnection connection = mock(JdbcConnection.class);
323323
DatabaseMetaData meta = new JdbcDatabaseMetaData(connection);
324324
try (ResultSet rs = meta.getClientInfoProperties()) {
325+
assertThat(rs.next(), is(true));
326+
assertThat(rs.getString("NAME"), is(equalTo("APPLICATIONNAME")));
327+
assertThat(rs.getString("DEFAULT_VALUE"), is(equalTo("")));
328+
329+
assertThat(rs.next(), is(true));
330+
assertThat(rs.getString("NAME"), is(equalTo("CLIENTHOSTNAME")));
331+
assertThat(rs.getString("DEFAULT_VALUE"), is(equalTo("")));
332+
333+
assertThat(rs.next(), is(true));
334+
assertThat(rs.getString("NAME"), is(equalTo("CLIENTUSER")));
335+
assertThat(rs.getString("DEFAULT_VALUE"), is(equalTo("")));
336+
325337
assertThat(rs.next(), is(false));
326338
ResultSetMetaData rsmd = rs.getMetaData();
327339
assertThat(rsmd.getColumnCount(), is(equalTo(4)));

0 commit comments

Comments
 (0)