diff --git a/src/main/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSet.java b/src/main/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSet.java
index fe697246..08808244 100644
--- a/src/main/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSet.java
+++ b/src/main/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSet.java
@@ -61,7 +61,12 @@
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -581,28 +586,10 @@ public void deleteRow() throws SQLException {
throw new BQSQLFeatureNotSupportedException("deleteRow()");
}
- /**
- *
- *
- *
Implementation Details:
- *
- *
- * Not implemented yet.
- *
- * @throws BQSQLException
- */
+ /** {@inheritDoc} */
@Override
public int findColumn(String columnLabel) throws SQLException {
- if (this.isClosed()) {
- throw new BQSQLException("This Resultset is Closed");
- }
- int columncount = this.getMetaData().getColumnCount();
- for (int i = 1; i <= columncount; i++) {
- if (this.getMetaData().getCatalogName(i).equals(columnLabel)) {
- return i;
- }
- }
- throw new BQSQLException("No Such column labeled: " + columnLabel);
+ return CommonResultSet.findColumn(columnLabel, getMetaData());
}
/**
diff --git a/src/main/java/net/starschema/clouddb/jdbc/BQResultSet.java b/src/main/java/net/starschema/clouddb/jdbc/BQResultSet.java
index ac361699..0bfebfe2 100644
--- a/src/main/java/net/starschema/clouddb/jdbc/BQResultSet.java
+++ b/src/main/java/net/starschema/clouddb/jdbc/BQResultSet.java
@@ -109,17 +109,7 @@ public BQResultSet(
/** {@inheritDoc} */
@Override
public int findColumn(String columnLabel) throws SQLException {
- if (this.isClosed()) {
- throw new BQSQLException("This Resultset is Closed");
- }
- int columncount = this.getMetaData().getColumnCount();
- for (int i = 1; i <= columncount; i++) {
- if (this.getMetaData().getCatalogName(i).equals(columnLabel)) {
- return i;
- }
- }
- SQLException e = new BQSQLException("No Such column labeled: " + columnLabel);
- throw e;
+ return CommonResultSet.findColumn(columnLabel, getMetaData());
}
/** {@inheritDoc} */
diff --git a/src/main/java/net/starschema/clouddb/jdbc/BQScrollableResultSet.java b/src/main/java/net/starschema/clouddb/jdbc/BQScrollableResultSet.java
index d51fef05..3cb03009 100644
--- a/src/main/java/net/starschema/clouddb/jdbc/BQScrollableResultSet.java
+++ b/src/main/java/net/starschema/clouddb/jdbc/BQScrollableResultSet.java
@@ -137,16 +137,7 @@ public BQScrollableResultSet(
/** {@inheritDoc} */
@Override
public int findColumn(String columnLabel) throws SQLException {
- if (this.isClosed()) {
- throw new BQSQLException("This Resultset is Closed");
- }
- int columncount = this.getMetaData().getColumnCount();
- for (int i = 1; i <= columncount; i++) {
- if (this.getMetaData().getCatalogName(i).equals(columnLabel)) {
- return i;
- }
- }
- throw new BQSQLException("No Such column labeled: " + columnLabel);
+ return CommonResultSet.findColumn(columnLabel, getMetaData());
}
/** {@inheritDoc} */
diff --git a/src/main/java/net/starschema/clouddb/jdbc/CommonResultSet.java b/src/main/java/net/starschema/clouddb/jdbc/CommonResultSet.java
new file mode 100644
index 00000000..be7a16d6
--- /dev/null
+++ b/src/main/java/net/starschema/clouddb/jdbc/CommonResultSet.java
@@ -0,0 +1,27 @@
+package net.starschema.clouddb.jdbc;
+
+import com.google.cloud.bigquery.BigQuerySQLException;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+
+/** A static utility class of methods common to all {@link java.sql.ResultSet} implementations. */
+class CommonResultSet {
+
+ /**
+ * A common implementation of {@link java.sql.ResultSet#findColumn(String)}
+ *
+ * @param label the column-to-find's label
+ * @param metadata the metadata from the {@link java.sql.ResultSet}
+ * @return the integer index of the labeled column, if it's found
+ * @throws SQLException if no column with the given label is found
+ */
+ static int findColumn(final String label, final ResultSetMetaData metadata) throws SQLException {
+ int columnCount = metadata.getColumnCount();
+ for (int column = 1; column <= columnCount; column++) {
+ if (metadata.getColumnLabel(column).equals(label)) {
+ return column;
+ }
+ }
+ throw new BigQuerySQLException("No such Column labeled: " + label);
+ }
+}
diff --git a/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java b/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java
index 71626bee..7c1b4a3f 100644
--- a/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java
+++ b/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java
@@ -28,6 +28,7 @@
import com.google.gson.Gson;
import java.io.IOException;
import java.math.BigDecimal;
+import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -58,7 +59,7 @@
* @author Horváth Attila
* @author Gunics Balazs
*/
-public class BQForwardOnlyResultSetFunctionTest {
+public class BQForwardOnlyResultSetFunctionTest extends CommonTestsForResultSets {
private static java.sql.Connection con = null;
private java.sql.ResultSet resultForTest = null;
@@ -845,4 +846,10 @@ public void testStatelessQuery() throws SQLException {
Assertions.assertThat(bqResultSet.getJobId()).isNull();
Assertions.assertThat(bqResultSet.getQueryId()).contains("!");
}
+
+ @Override
+ protected Statement createStatementForCommonTests(final Connection connection)
+ throws SQLException {
+ return connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ }
}
diff --git a/src/test/java/net/starschema/clouddb/jdbc/BQScrollableResultSetFunctionTest.java b/src/test/java/net/starschema/clouddb/jdbc/BQScrollableResultSetFunctionTest.java
index c7e81907..d0dfa483 100644
--- a/src/test/java/net/starschema/clouddb/jdbc/BQScrollableResultSetFunctionTest.java
+++ b/src/test/java/net/starschema/clouddb/jdbc/BQScrollableResultSetFunctionTest.java
@@ -22,6 +22,7 @@
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -31,6 +32,7 @@
import org.assertj.core.api.Assertions;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,7 +43,7 @@
* @author Horváth Attila
* @author Gunics Balazs
*/
-public class BQScrollableResultSetFunctionTest {
+public class BQScrollableResultSetFunctionTest extends CommonTestsForResultSets {
private static java.sql.Connection con = null;
private static java.sql.ResultSet Result = null;
@@ -745,4 +747,31 @@ public void testStatelessQuery() throws SQLException {
Assertions.assertThat(bqResultSet.getJobId()).isNull();
Assertions.assertThat(bqResultSet.getQueryId()).contains("!");
}
+
+ @Override
+ protected Statement createStatementForCommonTests(Connection connection) throws SQLException {
+ return connection.createStatement(
+ ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
+ }
+
+ @Override
+ @Test
+ @Ignore(
+ "Contradiction between BQSCrollableResultSet and BQForwardOnlyResultSet "
+ + "dates and times: b/317107706")
+ public void testGetTimeByLabel() throws SQLException {}
+
+ @Override
+ @Test
+ @Ignore(
+ "Contradiction between BQSCrollableResultSet and BQForwardOnlyResultSet "
+ + "dates and times: b/317107706")
+ public void testGetDateByLabel() throws SQLException {}
+
+ @Override
+ @Test
+ @Ignore(
+ "Contradiction between BQSCrollableResultSet and BQForwardOnlyResultSet "
+ + "dates and times: b/317107706")
+ public void testGetTimestampByLabel() throws SQLException {}
}
diff --git a/src/test/java/net/starschema/clouddb/jdbc/CommonTestsForResultSets.java b/src/test/java/net/starschema/clouddb/jdbc/CommonTestsForResultSets.java
new file mode 100644
index 00000000..a3b80a92
--- /dev/null
+++ b/src/test/java/net/starschema/clouddb/jdbc/CommonTestsForResultSets.java
@@ -0,0 +1,281 @@
+package net.starschema.clouddb.jdbc;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.stream.Collectors;
+import org.assertj.core.api.Assertions;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * An abstract class that contains tests common to {@link ResultSet} implementations. Its name
+ * doesn't end with Test so that JUnit doesn't try to run it.
+ *
+ * Subclass this in @{link ResultSet} test classes and implement {@link
+ * CommonTestsForResultSets#createStatementForCommonTests(Connection)}.
+ */
+public abstract class CommonTestsForResultSets {
+
+ private Connection connection;
+
+ @Before
+ public void connectForCommonTests() throws SQLException, IOException {
+ connection =
+ ConnectionFromResources.connect("installedaccount1.properties", "&useLegacySql=false");
+ }
+
+ @After
+ public void closeConnectionForCommonTests() throws SQLException {
+ connection.close();
+ }
+
+ /**
+ * A {@link java.util.function.Consumer} that can throw a {@link SQLException}
+ *
+ * @param The return value's type
+ */
+ private interface ConsumerWithSQLException {
+
+ void accept(T arg) throws SQLException;
+ }
+
+ /**
+ * Create a {@link Statement} that returns the appropriate result set.
+ *
+ * @param connection the connection from which to create the statement
+ * @return a statement used by common tests
+ * @throws SQLException if the {@link Connection} raises one
+ */
+ protected abstract Statement createStatementForCommonTests(final Connection connection)
+ throws SQLException;
+
+ /**
+ * Assert something about the single row and column returned by a query.
+ *
+ * @param query must result in a single row and column
+ * @param check a Consumer that can throw a {@link SQLException} - put your assertions about the
+ * result set in this
+ * @throws SQLException whenever check throws it
+ */
+ private void assertSingleRowAndColumn(
+ final String query, final ConsumerWithSQLException check) throws SQLException {
+ try (Statement stmt = createStatementForCommonTests(connection)) {
+ final ResultSet result = stmt.executeQuery(query);
+ Assertions.assertThat(result.next()).isTrue();
+ check.accept(result);
+ Assertions.assertThat(result.next()).isFalse();
+ }
+ }
+
+ @Test
+ public void testFindColumn() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 2 AS two", rs -> Assertions.assertThat(rs.findColumn("two")).isEqualTo(1));
+ }
+
+ /**
+ * A {@link java.util.function.Function} that can throw a {@link SQLException}
+ *
+ * @param the argument's type
+ * @param the returned value's type
+ */
+ private interface FunctionWithSQLException {
+ R apply(T arg) throws SQLException;
+ }
+
+ private void assertReaderAsString(
+ final String query,
+ final FunctionWithSQLException getter,
+ final String value)
+ throws SQLException {
+ assertSingleRowAndColumn(
+ query,
+ rs -> {
+ final Reader reader = getter.apply(rs);
+ final String contents =
+ new BufferedReader(reader).lines().collect(Collectors.joining(""));
+ Assertions.assertThat(contents).isEqualTo(value);
+ });
+ }
+
+ @Test
+ public void testGetAsciiStreamByLabel() throws SQLException {
+ assertReaderAsString(
+ "SELECT 'an ASCII string' AS stream",
+ rs -> new InputStreamReader(rs.getAsciiStream("stream"), StandardCharsets.US_ASCII),
+ "an ASCII string");
+ }
+
+ @Test
+ public void testGetBinaryStreamByLabel() throws SQLException {
+ assertReaderAsString(
+ "SELECT 'a binary string' AS stream",
+ rs -> new InputStreamReader(rs.getBinaryStream("stream"), StandardCharsets.US_ASCII),
+ "a binary string");
+ }
+
+ @Test
+ public void testGetBooleanByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT false AS bool", rs -> Assertions.assertThat(rs.getBoolean("bool")).isFalse());
+ }
+
+ @Test
+ public void testGetByteByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 98 AS byte", rs -> Assertions.assertThat(rs.getByte("byte")).isEqualTo((byte) 98));
+ }
+
+ @Test
+ public void testGetBytesByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 'abc' AS bytes",
+ rs -> Assertions.assertThat(rs.getBytes("bytes")).isEqualTo(new byte[] {'a', 'b', 'c'}));
+ }
+
+ @Test
+ public void testGetCharacterStreamByLabel() throws SQLException {
+ assertReaderAsString(
+ "SELECT 'some characters' AS chars",
+ rs -> rs.getCharacterStream("chars"),
+ "some characters");
+ }
+
+ @Test
+ public void testGetDateByLabel() throws SQLException {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+ calendar.set(2023, Calendar.DECEMBER, 19);
+ final Date expected = calendar.getTime();
+
+ assertSingleRowAndColumn(
+ "SELECT '2023-12-19' AS date",
+ rs -> {
+ final Date value = rs.getDate("date");
+ Assertions.assertThat(value).isEqualTo(expected);
+ });
+
+ assertSingleRowAndColumn(
+ "SELECT '2023-12-19' AS date",
+ rs -> {
+ final Date value = rs.getDate("date", calendar);
+ Assertions.assertThat(value).isEqualTo(expected);
+ });
+ }
+
+ @Test
+ public void testGetDoubleByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 0.1 AS double",
+ rs ->
+ Assertions.assertThat(rs.getDouble("double"))
+ .isEqualTo(0.1, Assertions.withPrecision(1d)));
+ }
+
+ @Test
+ public void testGetFloatByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 0.1 AS float",
+ rs ->
+ Assertions.assertThat(rs.getDouble("float"))
+ .isEqualTo(0.1, Assertions.withPrecision(1d)));
+ }
+
+ @Test
+ public void testGetIntByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 6 AS integer", rs -> Assertions.assertThat(rs.getInt("integer")).isEqualTo(6));
+ }
+
+ @Test
+ public void testGetLongByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 6 AS long", rs -> Assertions.assertThat(rs.getInt("long")).isEqualTo(6L));
+ }
+
+ @Test
+ public void testGetObjectByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 'an object' AS obj",
+ rs -> Assertions.assertThat(rs.getObject("obj")).isEqualTo("an object"));
+ }
+
+ @Test
+ public void testGetShortByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 32767 AS short",
+ rs -> Assertions.assertThat(rs.getShort("short")).isEqualTo((short) 32767));
+ }
+
+ @Test
+ public void testGetSQLXMLByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 'xml' AS xml",
+ rs -> {
+ final SQLXML xml = rs.getSQLXML("xml");
+ final String actual = xml.getString().trim();
+ final String expected = "xml";
+ Assertions.assertThat(actual).isEqualTo(expected);
+ });
+ }
+
+ @Test
+ public void testGetStringByLabel() throws SQLException {
+ assertSingleRowAndColumn(
+ "SELECT 'a string' AS string",
+ rs -> Assertions.assertThat(rs.getString("string")).isEqualTo("a string"));
+ }
+
+ @Test
+ public void testGetTimeByLabel() throws SQLException {
+ final Time expected = Time.valueOf("01:02:03");
+ assertSingleRowAndColumn(
+ "SELECT '01:02:03' AS time",
+ rs -> Assertions.assertThat(rs.getTime("time")).isEqualTo(expected));
+
+ final Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+ calendar.set(2023, 12, 19);
+ assertSingleRowAndColumn(
+ "SELECT '01:02:03' AS time",
+ rs -> Assertions.assertThat(rs.getTime("time", calendar)).isEqualTo(expected));
+ }
+
+ @Test
+ public void testGetTimestampByLabel() throws SQLException {
+ final Timestamp expected = Timestamp.valueOf("2023-12-19 01:02:03");
+ assertSingleRowAndColumn(
+ "SELECT '2023-12-19 01:02:03' AS timestamp",
+ rs -> Assertions.assertThat(rs.getTimestamp("timestamp")).isEqualTo(expected));
+
+ final Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+ calendar.set(2023, 12, 19);
+ assertSingleRowAndColumn(
+ "SELECT '2023-12-19 01:02:03' AS timestamp",
+ rs -> Assertions.assertThat(rs.getTimestamp("timestamp", calendar)).isEqualTo(expected));
+ }
+
+ @Test
+ public void testGetURLByLabel() throws SQLException, MalformedURLException {
+ final URL expected = new URL("https://127.0.0.1/p?q=1");
+ assertSingleRowAndColumn(
+ "SELECT 'https://127.0.0.1/p?q=1' AS url",
+ rs -> Assertions.assertThat(rs.getURL("url")).isEqualTo(expected));
+ }
+}