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

perf: use PLAN mode to get result metadata #388

Merged
merged 4 commits into from Mar 18, 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 @@ -29,7 +29,6 @@
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;
Expand Down Expand Up @@ -232,14 +231,6 @@ public void setArray(int parameterIndex, Array value) throws SQLException {
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();
Expand Down
Expand Up @@ -18,6 +18,7 @@

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;
Expand Down Expand Up @@ -147,6 +148,20 @@ private void resetStatementTimeout(StatementTimeout originalTimeout) throws SQLE
}
}

/**
* 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.
*
Expand All @@ -157,11 +172,24 @@ private void resetStatementTimeout(StatementTimeout originalTimeout) throws SQLE
*/
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 {
Expand Down
Expand Up @@ -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. */
Expand Down Expand Up @@ -86,4 +92,20 @@ public JdbcParameterMetaData getParameterMetaData() throws SQLException {
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();
}
}
}
Expand Up @@ -25,6 +25,7 @@
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;
Expand Down Expand Up @@ -293,7 +294,7 @@ public void testGetResultSetMetadata() throws SQLException {
.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();
Expand All @@ -306,4 +307,15 @@ public void testGetResultSetMetadata() throws SQLException {
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());
}
}
}
Expand Up @@ -16,18 +16,20 @@

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;
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;
Expand All @@ -38,6 +40,7 @@
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;
Expand Down Expand Up @@ -222,7 +225,7 @@ private static Long[] parseLongArray(String value) {
}

private static byte[] parseBytes(String value) {
return Base64.decodeBase64(value);
return BaseEncoding.base64().decode(value);
}

private List<Singer> createSingers() {
Expand Down Expand Up @@ -825,6 +828,53 @@ public void test08_InsertAllColumnTypes() throws SQLException {
}
}

@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());
Expand Down