Repository: googleapis/java-spanner-jdbc
Pull request #395 (2021-03-18 01:17:25)

chore(deps): update dependency com.google.cloud:libraries-bom to v19.2.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:libraries-bom](https://togithub.com/GoogleCloudPlatform/cloud-opensource-java) | `19.1.0` -> `19.2.0` | [![age](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/19.2.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/19.2.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/19.2.0/compatibility-slim/19.1.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/maven/com.google.cloud:libraries-bom/19.2.0/confidence-slim/19.1.0)](https://docs.renovatebot.com/merge-confidence/) |

---

### 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/395?src=pr&el=h1) Report
> Merging [#395](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/395?src=pr&el=desc) (e103905) into [master](https://codecov.io/gh/googleapis/java-spanner-jdbc/commit/ca852a70a971ac096be508c29fc06533f8290c0c?el=desc) (ca852a7) will **increase** coverage by `0.02%`.
> The diff coverage is `n/a`.

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

```diff
@@             Coverage Diff              @@
##             master     #395      +/-   ##
============================================
+ Coverage     72.10%   72.13%   +0.02%     
  Complexity     1136     1136              
============================================
  Files            24       24              
  Lines          3538     3538              
  Branches        539      539              
============================================
+ Hits           2551     2552       +1     
  Misses          767      767              
+ Partials        220      219       -1     
```


| [Impacted Files](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/395?src=pr&el=tree) | Coverage Δ | Complexity Δ | |
|---|---|---|---|
| [...ud/spanner/jdbc/AbstractJdbcPreparedStatement.java](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/395/diff?src=pr&el=tree#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvamRiYy9BYnN0cmFjdEpkYmNQcmVwYXJlZFN0YXRlbWVudC5qYXZh) | `90.24% <0.00%> (+0.60%)` | `51.00% <0.00%> (ø%)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/395?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/395?src=pr&el=footer). Last update [ca852a7...e103905](https://codecov.io/gh/googleapis/java-spanner-jdbc/pull/395?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/pom.xml b/pom.xml
index d787d0a..3900685 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,7 +71,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-spanner-bom</artifactId>
-        <version>5.0.0</version>
+        <version>5.1.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
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 f777289..6890bf9 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.1.0</version>
+        <version>19.2.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());