Repository: googleapis/java-spanner-jdbc
Pull request #392 (2021-03-11 01:27:28)

chore(deps): update dependency com.google.cloud:google-cloud-spanner-jdbc to v1.21.0
[![WhiteSource Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [com.google.cloud:google-cloud-spanner-jdbc](https://togithub.com/googleapis/java-spanner-jdbc) | `1.20.1` -> `1.21.0` | [![age](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner-jdbc/1.21.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner-jdbc/1.21.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner-jdbc/1.21.0/compatibility-slim/1.20.1)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.cloud:google-cloud-spanner-jdbc/1.21.0/confidence-slim/1.20.1)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>googleapis/java-spanner-jdbc</summary>

### [`v1.21.0`](https://togithub.com/googleapis/java-spanner-jdbc/blob/master/CHANGELOG.md#&#8203;1210-httpswwwgithubcomgoogleapisjava-spanner-jdbccomparev1201v1210-2021-03-10)

[Compare Source](https://togithub.com/googleapis/java-spanner-jdbc/compare/v1.20.1...v1.21.0)

##### Features

-   add support for CommitStats ([#&#8203;261](https://www.github.com/googleapis/java-spanner-jdbc/issues/261)) ([b32e7ae](https://www.github.com/googleapis/java-spanner-jdbc/commit/b32e7aebd4c8d24d052e4616b5dd7735878e01c3))
-   allow using UUID in PreparedStatement ([#&#8203;365](https://www.github.com/googleapis/java-spanner-jdbc/issues/365)) ([4cbee6d](https://www.github.com/googleapis/java-spanner-jdbc/commit/4cbee6dcc2dfea2515437b55b0ecfc956205d739)), closes [#&#8203;364](https://www.github.com/googleapis/java-spanner-jdbc/issues/364)

##### Dependencies

-   update dependency com.google.cloud:google-cloud-shared-dependencies to v0.20.1 ([#&#8203;384](https://www.github.com/googleapis/java-spanner-jdbc/issues/384)) ([f0cdf11](https://www.github.com/googleapis/java-spanner-jdbc/commit/f0cdf117e20325601f1d1c13641267add5b39955))
-   update dependency com.google.cloud:google-cloud-spanner-bom to v5 ([#&#8203;386](https://www.github.com/googleapis/java-spanner-jdbc/issues/386)) ([910c50c](https://www.github.com/googleapis/java-spanner-jdbc/commit/910c50c611cb30b96a2d5d0472afd2d8e3687013))

##### [1.20.1](https://www.github.com/googleapis/java-spanner-jdbc/compare/v1.20.0...v1.20.1) (2021-02-26)

##### Dependencies

-   update dependency com.google.cloud:google-cloud-shared-dependencies to v0.20.0 ([#&#8203;374](https://www.github.com/googleapis/java-spanner-jdbc/issues/374)) ([398d886](https://www.github.com/googleapis/java-spanner-jdbc/commit/398d8864a72029a62b0a3adfaeafaeae76af3e1a))
-   update dependency com.google.cloud:google-cloud-spanner-bom to v4.0.2 ([#&#8203;369](https://www.github.com/googleapis/java-spanner-jdbc/issues/369)) ([3a984c2](https://www.github.com/googleapis/java-spanner-jdbc/commit/3a984c26076f187a710b59c8487e330636319f7c))

</details>

---

### Renovate configuration

:date: **Schedule**: At any time (no schedule defined).

:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

:recycle: **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-spanner-jdbc).

codecov[bot]: # [Codecov](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/392?src=pr&el=h1) Report
> Merging [#392](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/392?src=pr&el=desc) (9be87fb) into [master](https://codecov.io/gh/googleapis/java-spanner-jdbc/commit/004e4ac69a4ae5530b5ad63ed20546704ed5681b?el=desc) (004e4ac) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/392/graphs/tree.svg?width=650&height=150&src=pr&token=ODlczvbGrj)](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/392?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##             master     #392   +/-   ##
=========================================
  Coverage     72.13%   72.13%           
  Complexity     1136     1136           
=========================================
  Files            24       24           
  Lines          3538     3538           
  Branches        539      539           
=========================================
  Hits           2552     2552           
  Misses          767      767           
  Partials        219      219           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/392?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/392?src=pr&el=footer). Last update [004e4ac...9be87fb](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/392?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index e70c468..0edee34 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -29,7 +29,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-spanner-jdbc</artifactId>
-      <version>1.20.1</version>
+      <version>1.21.0</version>
     </dependency>
     <!-- [END spanner-jdbc_install_without_bom] -->
 
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index fbdf508..f777289 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -30,7 +30,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>libraries-bom</artifactId>
-        <version>19.0.0</version>
+        <version>19.1.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java
index bbd5975..0940997 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java
@@ -29,7 +29,6 @@ import java.sql.NClob;
 import java.sql.PreparedStatement;
 import java.sql.Ref;
 import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
 import java.sql.RowId;
 import java.sql.SQLException;
 import java.sql.SQLXML;
@@ -232,14 +231,6 @@ abstract class AbstractJdbcPreparedStatement extends JdbcStatement implements Pr
     parameters.setParameter(parameterIndex, value, Types.ARRAY);
   }
 
-  @Override
-  public ResultSetMetaData getMetaData() throws SQLException {
-    checkClosed();
-    try (ResultSet rs = executeQuery()) {
-      return rs.getMetaData();
-    }
-  }
-
   @Override
   public void setDate(int parameterIndex, Date value, Calendar cal) throws SQLException {
     checkClosed();
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java
index 48a002d..5217396 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java
@@ -18,6 +18,7 @@ package com.google.cloud.spanner.jdbc;
 
 import com.google.cloud.spanner.Options;
 import com.google.cloud.spanner.Options.QueryOption;
+import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
 import com.google.cloud.spanner.SpannerException;
 import com.google.cloud.spanner.connection.StatementResult;
 import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
@@ -147,6 +148,20 @@ abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Stat
     }
   }
 
+  /**
+   * Executes a SQL statement on the connection of this {@link Statement} as a query using the given
+   * {@link QueryAnalyzeMode}.
+   *
+   * @param statement the SQL statement to executed
+   * @param analyzeMode the {@link QueryAnalyzeMode} to use
+   * @return the result of the SQL statement as a {@link ResultSet}
+   * @throws SQLException if a database error occurs.
+   */
+  ResultSet analyzeQuery(com.google.cloud.spanner.Statement statement, QueryAnalyzeMode analyzeMode)
+      throws SQLException {
+    return executeQuery(statement, analyzeMode);
+  }
+
   /**
    * Executes a SQL statement on the connection of this {@link Statement} as a query.
    *
@@ -157,11 +172,24 @@ abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Stat
    */
   ResultSet executeQuery(com.google.cloud.spanner.Statement statement, QueryOption... options)
       throws SQLException {
+    return executeQuery(statement, null, options);
+  }
+
+  private ResultSet executeQuery(
+      com.google.cloud.spanner.Statement statement,
+      QueryAnalyzeMode analyzeMode,
+      QueryOption... options)
+      throws SQLException {
     StatementTimeout originalTimeout = setTemporaryStatementTimeout();
     try {
-      return JdbcResultSet.of(
-          this,
-          connection.getSpannerConnection().executeQuery(statement, getQueryOptions(options)));
+      com.google.cloud.spanner.ResultSet resultSet;
+      if (analyzeMode == null) {
+        resultSet =
+            connection.getSpannerConnection().executeQuery(statement, getQueryOptions(options));
+      } else {
+        resultSet = connection.getSpannerConnection().analyzeQuery(statement, analyzeMode);
+      }
+      return JdbcResultSet.of(this, resultSet);
     } catch (SpannerException e) {
       throw JdbcSqlExceptionFactory.of(e);
     } finally {
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java
index 7a0e842..d8eeb62 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java
@@ -17,11 +17,17 @@
 package com.google.cloud.spanner.jdbc;
 
 import com.google.cloud.spanner.Options.QueryOption;
+import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
+import com.google.cloud.spanner.ResultSets;
 import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.Struct;
+import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.connection.StatementParser;
 import com.google.cloud.spanner.jdbc.JdbcParameterStore.ParametersInfo;
+import com.google.common.collect.ImmutableList;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 
 /** Implementation of {@link PreparedStatement} for Cloud Spanner. */
@@ -86,4 +92,20 @@ class JdbcPreparedStatement extends AbstractJdbcPreparedStatement {
     checkClosed();
     return new JdbcParameterMetaData(this);
   }
+
+  @Override
+  public ResultSetMetaData getMetaData() throws SQLException {
+    checkClosed();
+    if (StatementParser.INSTANCE.isUpdateStatement(sql)) {
+      // Return metadata for an empty result set as DML statements do not return any results (as a
+      // result set).
+      com.google.cloud.spanner.ResultSet resultSet =
+          ResultSets.forRows(Type.struct(), ImmutableList.<Struct>of());
+      resultSet.next();
+      return new JdbcResultSetMetaData(JdbcResultSet.of(resultSet), this);
+    }
+    try (ResultSet rs = analyzeQuery(createStatement(), QueryAnalyzeMode.PLAN)) {
+      return rs.getMetaData();
+    }
+  }
 }


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java
index 85334b2..5a1235f 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatementTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
 import com.google.cloud.spanner.ResultSet;
 import com.google.cloud.spanner.ResultSets;
 import com.google.cloud.spanner.Statement;
@@ -293,7 +294,7 @@ public class JdbcPreparedStatementTest {
                     .set("AMOUNT")
                     .to(Math.PI)
                     .build()));
-    when(connection.executeQuery(Statement.of(sql))).thenReturn(rs);
+    when(connection.analyzeQuery(Statement.of(sql), QueryAnalyzeMode.PLAN)).thenReturn(rs);
     try (JdbcPreparedStatement ps =
         new JdbcPreparedStatement(createMockConnection(connection), sql)) {
       ResultSetMetaData metadata = ps.getMetaData();
@@ -306,4 +307,15 @@ public class JdbcPreparedStatementTest {
       assertEquals(Types.DOUBLE, metadata.getColumnType(3));
     }
   }
+
+  @Test
+  public void testGetResultSetMetaDataForDml() throws SQLException {
+    Connection connection = mock(Connection.class);
+    try (JdbcPreparedStatement ps =
+        new JdbcPreparedStatement(
+            createMockConnection(connection), "UPDATE FOO SET BAR=1 WHERE TRUE")) {
+      ResultSetMetaData metadata = ps.getMetaData();
+      assertEquals(0, metadata.getColumnCount());
+    }
+  }
 }
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java
index b4e02a9..2890ed9 100644
--- a/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/jdbc/it/ITJdbcPreparedStatementTest.java
@@ -16,6 +16,7 @@
 
 package com.google.cloud.spanner.jdbc.it;
 
+import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -23,11 +24,12 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
-import com.google.api.client.util.Base64;
 import com.google.cloud.spanner.IntegrationTest;
 import com.google.cloud.spanner.jdbc.ITAbstractJdbcTest;
 import com.google.common.base.Strings;
+import com.google.common.io.BaseEncoding;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.StringReader;
@@ -38,6 +40,7 @@ import java.sql.Date;
 import java.sql.ParameterMetaData;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Timestamp;
@@ -222,7 +225,7 @@ public class ITJdbcPreparedStatementTest extends ITAbstractJdbcTest {
   }
 
   private static byte[] parseBytes(String value) {
-    return Base64.decodeBase64(value);
+    return BaseEncoding.base64().decode(value);
   }
 
   private List<Singer> createSingers() {
@@ -825,6 +828,53 @@ public class ITJdbcPreparedStatementTest extends ITAbstractJdbcTest {
     }
   }
 
+  @Test
+  public void test09_MetaData_FromQuery() throws SQLException {
+    assumeFalse("The emulator does not support PLAN mode", isUsingEmulator());
+    try (Connection con = createConnection()) {
+      try (PreparedStatement ps =
+          con.prepareStatement("SELECT * FROM TableWithAllColumnTypes WHERE ColInt64=?")) {
+        ResultSetMetaData metadata = ps.getMetaData();
+        assertEquals(22, metadata.getColumnCount());
+        int index = 0;
+        assertEquals("ColInt64", metadata.getColumnLabel(++index));
+        assertEquals("ColFloat64", metadata.getColumnLabel(++index));
+        assertEquals("ColBool", metadata.getColumnLabel(++index));
+        assertEquals("ColString", metadata.getColumnLabel(++index));
+        assertEquals("ColStringMax", metadata.getColumnLabel(++index));
+        assertEquals("ColBytes", metadata.getColumnLabel(++index));
+        assertEquals("ColBytesMax", metadata.getColumnLabel(++index));
+        assertEquals("ColDate", metadata.getColumnLabel(++index));
+        assertEquals("ColTimestamp", metadata.getColumnLabel(++index));
+        assertEquals("ColCommitTS", metadata.getColumnLabel(++index));
+        assertEquals("ColNumeric", metadata.getColumnLabel(++index));
+        assertEquals("ColInt64Array", metadata.getColumnLabel(++index));
+        assertEquals("ColFloat64Array", metadata.getColumnLabel(++index));
+        assertEquals("ColBoolArray", metadata.getColumnLabel(++index));
+        assertEquals("ColStringArray", metadata.getColumnLabel(++index));
+        assertEquals("ColStringMaxArray", metadata.getColumnLabel(++index));
+        assertEquals("ColBytesArray", metadata.getColumnLabel(++index));
+        assertEquals("ColBytesMaxArray", metadata.getColumnLabel(++index));
+        assertEquals("ColDateArray", metadata.getColumnLabel(++index));
+        assertEquals("ColTimestampArray", metadata.getColumnLabel(++index));
+        assertEquals("ColNumericArray", metadata.getColumnLabel(++index));
+        assertEquals("ColComputed", metadata.getColumnLabel(++index));
+      }
+    }
+  }
+
+  @Test
+  public void test10_MetaData_FromDML() throws SQLException {
+    try (Connection con = createConnection()) {
+      try (PreparedStatement ps =
+          con.prepareStatement(
+              "UPDATE TableWithAllColumnTypes SET ColBool=FALSE WHERE ColInt64=?")) {
+        ResultSetMetaData metadata = ps.getMetaData();
+        assertEquals(0, metadata.getColumnCount());
+      }
+    }
+  }
+
   private void assertDefaultParameterMetaData(ParameterMetaData pmd, int expectedParamCount)
       throws SQLException {
     assertEquals(expectedParamCount, pmd.getParameterCount());