diff --git a/google-cloud-bigquery-jdbc/Makefile b/google-cloud-bigquery-jdbc/Makefile index 61521770c7..dd0eb2a79d 100644 --- a/google-cloud-bigquery-jdbc/Makefile +++ b/google-cloud-bigquery-jdbc/Makefile @@ -127,4 +127,25 @@ docker-integration-test: .check-env docker-coverage: $(MAKE) .docker-run args="make unit-test-coverage" - $(MAKE) .docker-run args="make full-coverage" \ No newline at end of file + $(MAKE) .docker-run args="make full-coverage" + +# Standalone Tests Commands +DRIVER_JAR ?= target/google-cloud-bigquery-jdbc-0.4.0.jar +STANDALONE_JAR = target-it/google-cloud-bigquery-jdbc-it-0.4.0-standalone.jar + +build-standalone-tests: + mvn clean install -DskipTests + mvn -f pom-it.xml clean package + +run-standalone-tests: .check-env + @if [ ! -f "$(STANDALONE_JAR)" ]; then \ + echo "Standalone test JAR not found. Run 'make build-standalone-tests' first."; \ + exit 1; \ + fi + @if [ ! -f "$(DRIVER_JAR)" ]; then \ + echo "Driver JAR not found at $(DRIVER_JAR). Provide it using DRIVER_JAR=/path/to/driver.jar make run-standalone-tests"; \ + exit 1; \ + fi + @echo "Running standalone tests with driver: $(DRIVER_JAR)" + java -cp "$(STANDALONE_JAR):$(DRIVER_JAR)" \ + org.junit.runner.JUnitCore com.google.cloud.bigquery.jdbc.it.ITDriverAgnosticTest \ No newline at end of file diff --git a/google-cloud-bigquery-jdbc/pom-it.xml b/google-cloud-bigquery-jdbc/pom-it.xml new file mode 100644 index 0000000000..f2682c6330 --- /dev/null +++ b/google-cloud-bigquery-jdbc/pom-it.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + com.google.cloud + google-cloud-bigquery-parent + 2.60.0 + ../pom.xml + + + com.google.cloud + google-cloud-bigquery-jdbc-it + 0.4.0 + BigQuery JDBC IT Assembly + pom + + + true + true + true + + + + + com.google.cloud + google-cloud-bigquery-jdbc + 0.4.0 + test-jar + test + + + com.google.cloud + google-cloud-bigquery-jdbc + 0.4.0 + test + + + com.google.cloud + google-cloud-bigquery + test + + + com.google.cloud + google-cloud-bigquerystorage + test + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + org.mockito + mockito-core + 4.11.0 + test + + + + + target-it + + + org.apache.maven.plugins + maven-assembly-plugin + 3.6.0 + + + make-test-assembly + package + + single + + + + src/assembly/test-assembly.xml + + + + + + + + diff --git a/google-cloud-bigquery-jdbc/src/assembly/test-assembly.xml b/google-cloud-bigquery-jdbc/src/assembly/test-assembly.xml new file mode 100644 index 0000000000..32f1bb9acd --- /dev/null +++ b/google-cloud-bigquery-jdbc/src/assembly/test-assembly.xml @@ -0,0 +1,29 @@ + + standalone + + jar + + false + + + + / + false + true + test + + com.google.cloud:google-cloud-bigquery-jdbc:jar + + + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/*.MF + + + + + diff --git a/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java b/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java index 22f5c1c763..fab5883c56 100644 --- a/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java +++ b/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java @@ -16,7 +16,6 @@ package com.google.cloud.bigquery.jdbc.it; -import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -49,31 +48,17 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Paths; -import java.sql.CallableStatement; import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.Date; import java.sql.Driver; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.time.Instant; -import java.time.LocalTime; -import java.util.Arrays; -import java.util.Calendar; -import java.util.HashSet; import java.util.Properties; import java.util.Random; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.BiFunction; @@ -84,7 +69,7 @@ import org.junit.Ignore; import org.junit.Test; -public class ITBigQueryJDBCTest extends ITBase { +public class ITBigQueryJDBCTest extends ITDriverAgnosticTest { static final String PROJECT_ID = ServiceOptions.getDefaultProjectId(); static Connection bigQueryConnection; static BigQuery bigQuery; @@ -511,39 +496,6 @@ public void testFastQueryPathSmall() throws SQLException { assertEquals(850, resultSetRowCount(jsonResultSet)); } - @Test - public void testSmallSelectAndVerifyResults() throws SQLException { - String query = - "SELECT repository_name FROM `bigquery-public-data.samples.github_timeline` WHERE" - + " repository_name LIKE 'X%' LIMIT 10"; - - ResultSet resultSet = bigQueryStatement.executeQuery(query); - int rowCount = 0; - while (resultSet.next()) { - assertTrue(resultSet.getString(1).startsWith("X")); - rowCount++; - } - assertEquals(10, rowCount); - } - - @Test - // reads without using ReadAPI and makes sure that they are in order, which implies threads worked - // correctly - public void testIterateOrderJsonMultiThread_NoReadApi() throws SQLException { - int expectedCnt = 10000; - String query = String.format(BASE_QUERY, expectedCnt); - ResultSet rs = bigQueryStatementNoReadApi.executeQuery(query); - int cnt = 0; - double oldTriDis = 0.0d; - while (rs.next()) { - double tripDis = rs.getDouble("trip_distance"); - ++cnt; - assertTrue(oldTriDis <= tripDis); - oldTriDis = tripDis; - } - assertEquals(expectedCnt, cnt); // all the records were retrieved - } - @Test public void testInvalidQuery() throws SQLException { String query = "SELECT *"; @@ -867,43 +819,6 @@ public void testSetAutocommitWhenConnectionClosedThrows() throws SQLException { assertThrows(IllegalStateException.class, () -> connection.setAutoCommit(true)); } - @Test - public void testExecuteUpdate() throws SQLException { - String TABLE_NAME = "JDBC_EXECUTE_UPDATE_TABLE_" + randomNumber; - String createQuery = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER);", - DATASET, TABLE_NAME); - String insertQuery = - String.format( - "INSERT INTO %s.%s (StringField, IntegerField) " - + "VALUES ('string1',111 ), ('string2',111 ), ('string3',222 ), ('string4',333 );", - DATASET, TABLE_NAME); - String updateQuery = - String.format( - "UPDATE %s.%s SET StringField='Jane Doe' WHERE IntegerField=111", DATASET, TABLE_NAME); - String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); - String selectQuery = String.format("SELECT * FROM %s.%s", DATASET, TABLE_NAME); - - int createStatus = bigQueryStatement.executeUpdate(createQuery); - assertEquals(0, createStatus); - - int insertStatus = bigQueryStatement.executeUpdate(insertQuery); - assertEquals(4, insertStatus); - - bigQueryStatement.executeQuery(selectQuery); - int selectStatus = bigQueryStatement.getUpdateCount(); - assertEquals(-1, selectStatus); - - int updateStatus = bigQueryStatement.executeUpdate(updateQuery); - assertEquals(2, updateStatus); - - int dropStatus = bigQueryStatement.executeUpdate(dropQuery); - assertEquals(0, dropStatus); - - bigQueryStatement.execute(String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TABLE_NAME)); - } - @Test public void testExecuteQueryWithInsert() throws SQLException { String TABLE_NAME = "JDBC_EXECUTE_UPDATE_TABLE_" + randomNumber; @@ -934,98 +849,6 @@ public void testExecuteUpdateWithSelect() throws SQLException { assertThrows(BigQueryJdbcException.class, () -> bigQueryStatement.executeUpdate(selectQuery)); } - @Test - public void testExecuteMethod() throws SQLException { - - String TABLE_NAME = "JDBC_EXECUTE_TABLE_" + randomNumber; - String createQuery = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER);", - DATASET, TABLE_NAME); - String insertQuery = - String.format( - "INSERT INTO %s.%s (StringField, IntegerField) " - + "VALUES ('string1',111 ), ('string2',111 ), ('string3',222 ), ('string4',333 );", - DATASET, TABLE_NAME); - String updateQuery = - String.format( - "UPDATE %s.%s SET StringField='Jane Doe' WHERE IntegerField=111", DATASET, TABLE_NAME); - String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); - String selectQuery = String.format("SELECT * FROM %s.%s", DATASET, TABLE_NAME); - - boolean createStatus = bigQueryStatement.execute(createQuery); - assertFalse(createStatus); - - boolean insertStatus = bigQueryStatement.execute(insertQuery); - assertFalse(insertStatus); - - boolean selectStatus = bigQueryStatement.execute(selectQuery); - assertTrue(selectStatus); - int selectCount = bigQueryStatement.getUpdateCount(); - assertEquals(-1, selectCount); - ResultSet resultSet = bigQueryStatement.getResultSet(); - assertNotNull(resultSet); - - boolean updateStatus = bigQueryStatement.execute(updateQuery); - assertFalse(updateStatus); - - boolean dropStatus = bigQueryStatement.execute(dropQuery); - assertFalse(dropStatus); - } - - @Test - public void testPreparedExecuteMethod() throws SQLException { - - String TABLE_NAME = "JDBC_PREPARED_EXECUTE_TABLE_" + randomNumber; - String createQuery = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER);", - DATASET, TABLE_NAME); - String insertQuery = - String.format( - "INSERT INTO %s.%s (StringField, IntegerField) VALUES (?,?), (?,?), (?,?), (?,?);", - DATASET, TABLE_NAME); - String updateQuery = - String.format("UPDATE %s.%s SET StringField=? WHERE IntegerField=?", DATASET, TABLE_NAME); - String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); - String selectQuery = String.format("SELECT ? FROM %s.%s", DATASET, TABLE_NAME); - - boolean createStatus = bigQueryStatement.execute(createQuery); - assertFalse(createStatus); - - PreparedStatement insertStmt = bigQueryConnection.prepareStatement(insertQuery); - insertStmt.setString(1, "String1"); - insertStmt.setInt(2, 111); - insertStmt.setString(3, "String2"); - insertStmt.setInt(4, 222); - insertStmt.setString(5, "String3"); - insertStmt.setInt(6, 333); - insertStmt.setString(7, "String4"); - insertStmt.setInt(8, 444); - - boolean insertStatus = insertStmt.execute(); - assertFalse(insertStatus); - - PreparedStatement selectStmt = bigQueryConnection.prepareStatement(selectQuery); - selectStmt.setString(1, "StringField"); - boolean selectStatus = selectStmt.execute(); - assertTrue(selectStatus); - - int selectCount = selectStmt.getUpdateCount(); - assertEquals(-1, selectCount); - ResultSet resultSet = selectStmt.getResultSet(); - assertNotNull(resultSet); - - PreparedStatement updateStmt = bigQueryConnection.prepareStatement(updateQuery); - updateStmt.setString(1, "Jane Doe"); - updateStmt.setInt(2, 222); - boolean updateStatus = updateStmt.execute(); - assertFalse(updateStatus); - - boolean dropStatus = bigQueryStatement.execute(dropQuery); - assertFalse(dropStatus); - } - @Test public void testPreparedStatementThrowsSyntaxError() throws SQLException { String TABLE_NAME = "JDBC_PREPARED_SYNTAX_ERR_TABLE_" + randomNumber; @@ -1200,17 +1023,6 @@ public void testEmptyExecuteBatch() throws SQLException { connection.close(); } - @Test - public void testNonValidStatementTypeForAddBatchThrows() { - String BATCH_TABLE = "JDBC_EXECUTE_BATCH_TABLE_NON_VALID_TYPE_" + random.nextInt(99); - String createBatchTable = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);", - DATASET, BATCH_TABLE); - assertThrows( - IllegalArgumentException.class, () -> bigQueryStatement.addBatch(createBatchTable)); - } - @Test public void testAllValidStatementTypesForAddBatch() throws SQLException { // setup @@ -1252,29 +1064,6 @@ public void testAllValidStatementTypesForAddBatch() throws SQLException { connection.close(); } - @Test - public void testUnsupportedHTAPIFallbacksToStandardQueriesWithRange() throws SQLException { - String selectQuery = "select * from `DATATYPERANGETEST.RangeIntervalTestTable` LIMIT 5000;"; - String connection_uri = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" - + "OAuthType=3;ProjectId=" - + PROJECT_ID - + ";MaxResults=500;HighThroughputActivationRatio=1;" - + "HighThroughputMinTableSize=100;" - + "EnableHighThroughputAPI=1;UnsupportedHTAPIFallback=1;JobCreationMode=1;"; - - // Read data via JDBC - Connection connection = DriverManager.getConnection(connection_uri); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery(selectQuery); - assertNotNull(resultSet); - - ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); - resultSet.next(); - assertEquals(3, resultSetMetaData.getColumnCount()); - connection.close(); - } - @Test public void testIntervalDataTypeWithArrowResultSet() throws SQLException { String selectQuery = @@ -1325,27 +1114,6 @@ public void testIntervalDataTypeWithJsonResultSet() throws SQLException { connection.close(); } - @Test - public void testValidLEPEndpointQuery() throws SQLException { - String DATASET = "JDBC_REGIONAL_DATASET"; - String TABLE_NAME = "REGIONAL_TABLE"; - String selectQuery = "select * from " + DATASET + "." + TABLE_NAME; - String connection_uri = - "jdbc:bigquery://https://googleapis.com/bigquery/v2:443;" - + "OAuthType=3;" - + "ProjectId=" - + PROJECT_ID - + ";" - + "EndpointOverrides=BIGQUERY=https://us-east4-bigquery.googleapis.com;"; - - // Read data via JDBC - Connection connection = DriverManager.getConnection(connection_uri); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery(selectQuery); - assertNotNull(resultSet.getMetaData()); - connection.close(); - } - @Test public void testValidEndpointWithInvalidBQPortThrows() throws SQLException { String TABLE_NAME = "JDBC_REGIONAL_TABLE_" + randomNumber; @@ -1385,27 +1153,6 @@ public void testLEPEndpointDataNotFoundThrows() throws SQLException { connection.close(); } - @Test - public void testValidREPEndpointQuery() throws SQLException { - String DATASET = "JDBC_REGIONAL_DATASET"; - String TABLE_NAME = "REGIONAL_TABLE"; - String selectQuery = "select * from " + DATASET + "." + TABLE_NAME; - String connection_uri = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" - + "OAuthType=3;" - + "ProjectId=" - + PROJECT_ID - + ";" - + "EndpointOverrides=BIGQUERY=https://bigquery.us-east4.rep.googleapis.com;"; - - // Read data via JDBC - Connection connection = DriverManager.getConnection(connection_uri); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery(selectQuery); - assertNotNull(resultSet.getMetaData()); - connection.close(); - } - @Test public void testREPEndpointDataNotFoundThrows() throws SQLException { String DATASET = "JDBC_REGIONAL_DATASET"; @@ -1426,69 +1173,6 @@ public void testREPEndpointDataNotFoundThrows() throws SQLException { connection.close(); } - @Test - public void testCloseStatement() throws SQLException { - String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10"; - Statement statement = bigQueryConnection.createStatement(); - ResultSet jsonResultSet = statement.executeQuery(query); - assertEquals(10, resultSetRowCount(jsonResultSet)); - statement.close(); - assertTrue(statement.isClosed()); - } - - @Test - public void testCloseableStatementSingleResult() throws SQLException { - String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10"; - Statement statement = bigQueryConnection.createStatement(); - statement.closeOnCompletion(); - assertTrue(statement.isCloseOnCompletion()); - ResultSet jsonResultSet = statement.executeQuery(query); - assertFalse(statement.isClosed()); - jsonResultSet.close(); - assertTrue(statement.isClosed()); - } - - @Test - public void testCloseableStatementMultiResult() throws SQLException { - String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10;"; - Statement statement = bigQueryConnection.createStatement(); - statement.closeOnCompletion(); - assertTrue(statement.isCloseOnCompletion()); - statement.execute(query + query); - assertNotNull(statement.getResultSet()); - assertFalse(statement.isClosed()); - - assertTrue(statement.getMoreResults()); - assertNotNull(statement.getResultSet()); - assertFalse(statement.isClosed()); - - assertFalse(statement.getMoreResults()); - assertTrue(statement.isClosed()); - } - - @Test - public void testCloseableStatementMultiResultExplicitClose() throws SQLException { - String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10;"; - Statement statement = bigQueryConnection.createStatement(); - statement.closeOnCompletion(); - assertTrue(statement.isCloseOnCompletion()); - statement.execute(query + query); - ResultSet result = statement.getResultSet(); - result.close(); - assertFalse(statement.isClosed()); - - assertTrue(statement.getMoreResults()); - result = statement.getResultSet(); - result.close(); - assertTrue(statement.isClosed()); - } - - @Test - public void testConnectionIsValid() throws SQLException { - assertTrue(bigQueryConnection.isValid(10)); - assertTrue(bigQueryConnectionNoReadApi.isValid(10)); - } - @Test public void testDataSource() throws SQLException { DataSource ds = new DataSource(); @@ -1527,140 +1211,6 @@ public void testPreparedStatementSmallSelect() throws SQLException { assertTrue(jsonResultSet.getClass().getName().contains("BigQueryJsonResultSet")); } - @Test - public void testPreparedStatementExecuteUpdate() throws SQLException { - Random random = new Random(); - String DATASET = "JDBC_INTEGRATION_DATASET"; - String TABLE_NAME1 = "Inventory" + random.nextInt(9999); - String TABLE_NAME2 = "DetailedInventory" + random.nextInt(9999); - - String createQuery = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`product` STRING, `quantity` INTEGER);", - DATASET, TABLE_NAME1); - - String createQuery2 = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`product` STRING, `quantity` INTEGER," - + " `supply_constrained` BOOLEAN, `comment` STRING);", - DATASET, TABLE_NAME2); - - String insertQuery2 = - String.format( - "INSERT INTO %s.%s (product, quantity, supply_constrained, comment) " - + "VALUES ('countertop microwave', 20, NULL,'[]' )," - + " ('front load washer', 20, false,'[]' ), " - + " ('microwave', 20, false,'[]' ), " - + " ('refrigerator', 10, false,'[]' );", - DATASET, TABLE_NAME2); - - bigQueryStatement.execute(createQuery); - bigQueryStatement.execute(createQuery2); - bigQueryStatement.execute(insertQuery2); - - String insertQuery = - String.format( - "INSERT INTO %s.%s (product, quantity) " + "VALUES (?,? ), (?,? );", - DATASET, TABLE_NAME1); - PreparedStatement insertPs = bigQueryConnection.prepareStatement(insertQuery); - insertPs.setString(1, "dishwasher"); - insertPs.setInt(2, 30); - insertPs.setString(3, "dryer"); - insertPs.setInt(4, 30); - - int insertStatus = insertPs.executeUpdate(); - assertEquals(2, insertStatus); - - String updateQuery = - String.format("UPDATE %s.%s SET quantity=? WHERE product=?", DATASET, TABLE_NAME1); - PreparedStatement updatePs = bigQueryConnection.prepareStatement(updateQuery); - updatePs.setString(2, "dryer"); - updatePs.setInt(1, 35); - - int updateStatus = updatePs.executeUpdate(); - assertEquals(1, updateStatus); - - String deleteQuery = String.format("DELETE FROM %s.%s WHERE product=?", DATASET, TABLE_NAME1); - PreparedStatement deletePs = bigQueryConnection.prepareStatement(deleteQuery); - deletePs.setString(1, "dishwasher"); - - int deleteStatus = deletePs.executeUpdate(); - assertEquals(1, deleteStatus); - - String mergeQuery = - String.format( - "MERGE %s.%s T\n" - + "USING %s.%s S\n" - + "ON T.product = S.product\n" - + "WHEN NOT MATCHED AND quantity < ? THEN\n" - + " INSERT(product, quantity, supply_constrained, comment)\n" - + " VALUES(product, quantity, true, ?)\n" - + "WHEN NOT MATCHED THEN\n" - + " INSERT(product, quantity, supply_constrained)\n" - + " VALUES(product, quantity, false)", - DATASET, TABLE_NAME2, DATASET, TABLE_NAME1); - PreparedStatement mergePs = bigQueryConnection.prepareStatement(mergeQuery); - mergePs.setInt(1, 20); - mergePs.setString(2, "comment" + random.nextInt(999)); - - int mergeStatus = mergePs.executeUpdate(); - assertEquals(1, mergeStatus); - - ResultSet rs = - bigQueryStatement.executeQuery( - String.format("SELECT COUNT(*) AS row_count\n" + "FROM %s.%s", DATASET, TABLE_NAME2)); - rs.next(); - assertEquals(5, rs.getInt(1)); - - String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME1); - int dropStatus = bigQueryStatement.executeUpdate(dropQuery); - assertEquals(0, dropStatus); - bigQueryStatement.execute(String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME2)); - } - - @Test - public void testPreparedStatementDateTimeValues() throws SQLException { - Random random = new Random(); - String DATASET = "JDBC_INTEGRATION_DATASET"; - String TABLE_NAME1 = "DateTimeTestTable" + random.nextInt(9999); - - final String createTableQuery = - "CREATE OR REPLACE TABLE " - + " `%s.%s` " - + " (\n" - + "`StringField` STRING,\n" - + "`IntegerField` INTEGER,\n" - + "`TimestampField` TIMESTAMP,\n" - + "`TimeField` TIME,\n" - + "`DateField` DATE\n" - + ");"; - - String insertQuery = - String.format("INSERT INTO %s.%s VALUES (?,?,?,?,? );", DATASET, TABLE_NAME1); - - bigQueryStatement.execute(String.format(createTableQuery, DATASET, TABLE_NAME1)); - - PreparedStatement insertPs = bigQueryConnection.prepareStatement(insertQuery); - insertPs.setString(1, "dishwasher"); - insertPs.setInt(2, 1); - insertPs.setTimestamp(3, Timestamp.from(Instant.now())); - insertPs.setTime(4, Time.valueOf(LocalTime.NOON)); - insertPs.setDate(5, Date.valueOf("2025-12-3")); - - int insertStatus = insertPs.executeUpdate(); - assertEquals(1, insertStatus); - - ResultSet rs = - bigQueryStatement.executeQuery( - String.format("SELECT COUNT(*) AS row_count\n" + "FROM %s.%s", DATASET, TABLE_NAME1)); - rs.next(); - assertEquals(1, rs.getInt(1)); - - String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME1); - int dropStatus = bigQueryStatement.executeUpdate(dropQuery); - assertEquals(0, dropStatus); - } - @Test public void testValidDestinationTableSavesQueriesWithLegacySQL() throws SQLException { // setup @@ -1840,1192 +1390,67 @@ public void testNonSelectForStandardDestinationTableDoesNotThrow() throws SQLExc } @Test - public void testTableConstraints() throws SQLException { - ResultSet primaryKey1 = - bigQueryConnection - .getMetaData() - .getPrimaryKeys(PROJECT_ID, CONSTRAINTS_DATASET, CONSTRAINTS_TABLE_NAME); - primaryKey1.next(); - assertEquals("id", primaryKey1.getString(4)); - assertFalse(primaryKey1.next()); - - ResultSet primaryKey2 = - bigQueryConnection - .getMetaData() - .getPrimaryKeys(PROJECT_ID, CONSTRAINTS_DATASET, CONSTRAINTS_TABLE_NAME2); - primaryKey2.next(); - assertEquals("first_name", primaryKey2.getString(4)); - primaryKey2.next(); - assertEquals("last_name", primaryKey2.getString(4)); - assertFalse(primaryKey2.next()); - - ResultSet foreignKeys = - bigQueryConnection - .getMetaData() - .getImportedKeys(PROJECT_ID, CONSTRAINTS_DATASET, CONSTRAINTS_TABLE_NAME); - foreignKeys.next(); - assertEquals(CONSTRAINTS_TABLE_NAME2, foreignKeys.getString(3)); - assertEquals("first_name", foreignKeys.getString(4)); - assertEquals("name", foreignKeys.getString(8)); - foreignKeys.next(); - assertEquals(CONSTRAINTS_TABLE_NAME2, foreignKeys.getString(3)); - assertEquals("last_name", foreignKeys.getString(4)); - assertEquals("second_name", foreignKeys.getString(8)); - foreignKeys.next(); - assertEquals(CONSTRAINTS_TABLE_NAME3, foreignKeys.getString(3)); - assertEquals("address", foreignKeys.getString(4)); - assertEquals("address", foreignKeys.getString(8)); - assertFalse(foreignKeys.next()); - - ResultSet crossReference = - bigQueryConnection - .getMetaData() - .getCrossReference( - PROJECT_ID, - CONSTRAINTS_DATASET, - CONSTRAINTS_TABLE_NAME2, - PROJECT_ID, - CONSTRAINTS_DATASET, - CONSTRAINTS_TABLE_NAME); - crossReference.next(); - assertEquals(CONSTRAINTS_TABLE_NAME2, crossReference.getString(3)); - assertEquals("first_name", crossReference.getString(4)); - assertEquals("name", crossReference.getString(8)); - crossReference.next(); - assertEquals("last_name", crossReference.getString(4)); - assertEquals("second_name", crossReference.getString(8)); - assertFalse(crossReference.next()); + public void testRangeDataTypeWithArrowResultSet() throws SQLException { + String selectQuery = + "select * from `DATATYPERANGETEST.RangeIntervalTestTable` order by intColumn limit 5000;"; + + String connection_uri = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" + + "OAuthType=3;ProjectId=" + + PROJECT_ID + + ";MaxResults=500;HighThroughputActivationRatio=1;" + + "HighThroughputMinTableSize=100;" + + "EnableHighThroughputAPI=1;JobCreationMode=1;"; + + // Read data via JDBC + Connection connection = DriverManager.getConnection(connection_uri); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(selectQuery); + assertTrue(resultSet.getClass().getName().contains("BigQueryArrowResultSet")); + resultSet.next(); + assertEquals("[2024-07-14, 2024-09-23)", resultSet.getString("rangeField")); + connection.close(); } @Test - public void testDatabaseMetadataGetCatalogs() throws SQLException { - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - try (ResultSet rs = databaseMetaData.getCatalogs()) { - assertNotNull("ResultSet from getCatalogs() should not be null", rs); - - ResultSetMetaData rsmd = rs.getMetaData(); - assertNotNull("ResultSetMetaData should not be null", rsmd); - assertEquals("Should have one column", 1, rsmd.getColumnCount()); - assertEquals("Column name should be TABLE_CAT", "TABLE_CAT", rsmd.getColumnName(1)); - - assertTrue("ResultSet should have one row", rs.next()); - assertEquals("Catalog name should match Project ID", PROJECT_ID, rs.getString("TABLE_CAT")); - assertFalse("ResultSet should have no more rows", rs.next()); - } + public void testPrepareCallFailureResultSetType() throws SQLException { + assertThrows( + BigQueryJdbcSqlFeatureNotSupportedException.class, + () -> + this.bigQueryConnection.prepareCall( + "call testProc", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY)); } @Test - public void testDatabaseMetadataGetProcedures() throws SQLException { - String DATASET = "JDBC_INTEGRATION_DATASET"; - String procedureName = "create_customer"; - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - ResultSet resultSet = databaseMetaData.getProcedures(PROJECT_ID, DATASET, procedureName); - while (resultSet.next()) { - assertEquals(PROJECT_ID, resultSet.getString("PROCEDURE_CAT")); - assertEquals(DATASET, resultSet.getString("PROCEDURE_SCHEM")); - assertEquals(procedureName, resultSet.getString("PROCEDURE_NAME")); - assertEquals(procedureName, resultSet.getString("SPECIFIC_NAME")); - assertEquals(DatabaseMetaData.procedureResultUnknown, resultSet.getInt("PROCEDURE_TYPE")); - } + public void testPrepareCallFailureResultSetConcurrency() throws SQLException { + assertThrows( + BigQueryJdbcSqlFeatureNotSupportedException.class, + () -> + this.bigQueryConnection.prepareCall( + "call testProc", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)); } @Test - public void testDatabaseMetadataGetProcedureColumns() throws SQLException { - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - - // --- Test Case 1: Specific schema and procedure, null column name pattern --- - String specificSchema = "JDBC_INTEGRATION_DATASET"; - String specificProcedure = "create_customer"; - ResultSet resultSet = - databaseMetaData.getProcedureColumns(PROJECT_ID, specificSchema, specificProcedure, null); - int specificProcRows = 0; - boolean foundNameParam = false; - boolean foundIdParam = false; - while (resultSet.next()) { - specificProcRows++; - assertEquals(PROJECT_ID, resultSet.getString("PROCEDURE_CAT")); - assertEquals(specificSchema, resultSet.getString("PROCEDURE_SCHEM")); - assertEquals(specificProcedure, resultSet.getString("PROCEDURE_NAME")); - assertEquals(specificProcedure, resultSet.getString("SPECIFIC_NAME")); - if ("name".equals(resultSet.getString("COLUMN_NAME"))) { - foundNameParam = true; - assertEquals(1, resultSet.getInt("ORDINAL_POSITION")); - } - if ("id".equals(resultSet.getString("COLUMN_NAME"))) { - foundIdParam = true; - assertEquals(2, resultSet.getInt("ORDINAL_POSITION")); - } - } - assertEquals("Should find 2 parameters for " + specificProcedure, 2, specificProcRows); - assertTrue("Parameter 'name' should be found", foundNameParam); - assertTrue("Parameter 'id' should be found", foundIdParam); - resultSet.close(); - - // --- Test Case 2: Specific schema, procedure, and column name pattern --- - String specificColumn = "name"; - resultSet = - databaseMetaData.getProcedureColumns( - PROJECT_ID, specificSchema, specificProcedure, specificColumn); - assertTrue("Should find the specific column 'name'", resultSet.next()); - assertEquals(PROJECT_ID, resultSet.getString("PROCEDURE_CAT")); - assertEquals(specificSchema, resultSet.getString("PROCEDURE_SCHEM")); - assertEquals(specificProcedure, resultSet.getString("PROCEDURE_NAME")); - assertEquals(specificColumn, resultSet.getString("COLUMN_NAME")); - assertEquals(1, resultSet.getInt("ORDINAL_POSITION")); - assertEquals( - (short) DatabaseMetaData.procedureColumnUnknown, resultSet.getShort("COLUMN_TYPE")); - assertEquals(java.sql.Types.NVARCHAR, resultSet.getInt("DATA_TYPE")); - assertEquals("NVARCHAR", resultSet.getString("TYPE_NAME")); - assertFalse("Should only find one row for exact column match", resultSet.next()); - resultSet.close(); - - // --- Test Case 3: Non-existent procedure --- - resultSet = - databaseMetaData.getProcedureColumns( - PROJECT_ID, specificSchema, "non_existent_procedure_xyz", null); - assertFalse("Should not find columns for a non-existent procedure", resultSet.next()); - resultSet.close(); + public void testPrepareCallFailureResultSetHoldability() throws SQLException { + assertThrows( + BigQueryJdbcSqlFeatureNotSupportedException.class, + () -> + this.bigQueryConnection.prepareCall( + "call testProc", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT)); } + // Integration tests for CallableStatement Setters and Getters + @Test - public void testDatabaseMetadataGetColumns() throws SQLException { - String DATASET = "JDBC_INTEGRATION_DATASET"; - String TABLE_NAME = "JDBC_DATATYPES_INTEGRATION_TEST_TABLE"; - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + public void testPooledConnectionDataSourceSuccess() throws SQLException { + String connectionUrl = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;OAuthType=3;ProjectId=testProject;ConnectionPoolSize=20;ListenerPoolSize=20;"; - // --- Test Case 1: Specific Column (StringField) --- - ResultSet resultSet = - databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "StringField"); - - assertTrue(resultSet.next()); - assertEquals(PROJECT_ID, resultSet.getString("TABLE_CAT")); - assertEquals(DATASET, resultSet.getString("TABLE_SCHEM")); - assertEquals(TABLE_NAME, resultSet.getString("TABLE_NAME")); - assertEquals("StringField", resultSet.getString("COLUMN_NAME")); - assertEquals("NVARCHAR", resultSet.getString("TYPE_NAME")); - resultSet.getObject("COLUMN_SIZE"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(6, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 2: All Columns --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, null); - assertTrue(resultSet.next()); - int count = 0; - do { - count++; - assertEquals(PROJECT_ID, resultSet.getString("TABLE_CAT")); - assertEquals(DATASET, resultSet.getString("TABLE_SCHEM")); - assertEquals(TABLE_NAME, resultSet.getString("TABLE_NAME")); - assertNotNull(resultSet.getString("COLUMN_NAME")); - } while (resultSet.next()); - assertEquals(16, count); - - // --- Test Case 3: Column Name Pattern Matching (%Field) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "%Time%"); - assertTrue(resultSet.next()); - count = 0; - do { - count++; - String columnName = resultSet.getString("COLUMN_NAME"); - assertTrue(columnName.contains("Time")); - } while (resultSet.next()); - assertEquals(3, count); - - // --- Test Case 4: Column Name Pattern Matching (Integer%) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "Integer%"); - assertTrue(resultSet.next()); - assertEquals("IntegerField", resultSet.getString("COLUMN_NAME")); - assertEquals("BIGINT", resultSet.getString("TYPE_NAME")); - assertEquals(19, resultSet.getInt("COLUMN_SIZE")); - assertEquals(0, resultSet.getInt("DECIMAL_DIGITS")); - assertEquals(10, resultSet.getInt("NUM_PREC_RADIX")); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(2, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 5: Specific Column (BooleanField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "BooleanField"); - assertTrue(resultSet.next()); - assertEquals("BooleanField", resultSet.getString("COLUMN_NAME")); - assertEquals("BOOLEAN", resultSet.getString("TYPE_NAME")); - assertEquals(1, resultSet.getInt("COLUMN_SIZE")); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(1, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 6: Specific Column (NumericField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "NumericField"); - assertTrue(resultSet.next()); - assertEquals("NumericField", resultSet.getString("COLUMN_NAME")); - assertEquals("NUMERIC", resultSet.getString("TYPE_NAME")); - assertEquals(38, resultSet.getInt("COLUMN_SIZE")); - assertEquals(9, resultSet.getInt("DECIMAL_DIGITS")); - assertEquals(10, resultSet.getInt("NUM_PREC_RADIX")); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(4, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 7: Specific Column (BytesField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "BytesField"); - assertTrue(resultSet.next()); - assertEquals("BytesField", resultSet.getString("COLUMN_NAME")); - assertEquals("VARBINARY", resultSet.getString("TYPE_NAME")); - resultSet.getObject("COLUMN_SIZE"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(7, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 8: Specific Column (ArrayField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "ArrayField"); - assertTrue(resultSet.next()); - assertEquals("ArrayField", resultSet.getString("COLUMN_NAME")); - assertEquals("ARRAY", resultSet.getString("TYPE_NAME")); - resultSet.getObject("COLUMN_SIZE"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(9, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 9: Specific Column (TimestampField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "TimestampField"); - assertTrue(resultSet.next()); - assertEquals("TimestampField", resultSet.getString("COLUMN_NAME")); - assertEquals("TIMESTAMP", resultSet.getString("TYPE_NAME")); - assertEquals(29, resultSet.getInt("COLUMN_SIZE")); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(10, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 10: Specific Column (DateField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "DateField"); - assertTrue(resultSet.next()); - assertEquals("DateField", resultSet.getString("COLUMN_NAME")); - assertEquals("DATE", resultSet.getString("TYPE_NAME")); - assertEquals(10, resultSet.getInt("COLUMN_SIZE")); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(11, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 11: Specific Column (TimeField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "TimeField"); - assertTrue(resultSet.next()); - assertEquals("TimeField", resultSet.getString("COLUMN_NAME")); - assertEquals("TIME", resultSet.getString("TYPE_NAME")); - assertEquals(15, resultSet.getInt("COLUMN_SIZE")); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(12, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 12: Specific Column (DateTimeField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "DateTimeField"); - assertTrue(resultSet.next()); - assertEquals("DateTimeField", resultSet.getString("COLUMN_NAME")); - assertEquals("TIMESTAMP", resultSet.getString("TYPE_NAME")); - assertEquals(29, resultSet.getInt("COLUMN_SIZE")); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(13, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - - // --- Test Case 13: Specific Column (GeographyField) --- - resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "GeographyField"); - assertTrue(resultSet.next()); - assertEquals("GeographyField", resultSet.getString("COLUMN_NAME")); - assertEquals("VARCHAR", resultSet.getString("TYPE_NAME")); - resultSet.getObject("COLUMN_SIZE"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("DECIMAL_DIGITS"); - assertTrue(resultSet.wasNull()); - resultSet.getObject("NUM_PREC_RADIX"); - assertTrue(resultSet.wasNull()); - assertEquals(1, resultSet.getInt("NULLABLE")); - assertEquals(14, resultSet.getInt("ORDINAL_POSITION")); - assertFalse(resultSet.next()); - } - - @Test - public void testDatabaseMetadataGetTables() throws SQLException { - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - String DATASET = "JDBC_TABLE_TYPES_TEST"; - - // --- Test Case 1: Get all tables (types = null) --- - ResultSet rsAll = databaseMetaData.getTables(PROJECT_ID, DATASET, null, null); - Set allTableNames = new HashSet<>(); - while (rsAll.next()) { - allTableNames.add(rsAll.getString("TABLE_NAME")); - } - assertTrue(allTableNames.contains("base_table")); - assertTrue(allTableNames.contains("my_view")); - assertTrue(allTableNames.contains("external_table")); - assertTrue(allTableNames.contains("my_materialized_view")); - assertTrue(allTableNames.contains("base_table_clone")); - assertTrue(allTableNames.contains("base_table_snapshot")); - assertEquals(6, allTableNames.size()); - - // --- Test Case 2: Get only "TABLE" type --- - ResultSet rsTable = - databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"TABLE"}); - Set tableNames = new HashSet<>(); - while (rsTable.next()) { - tableNames.add(rsTable.getString("TABLE_NAME")); - } - assertTrue(tableNames.contains("base_table")); - assertTrue(tableNames.contains("base_table_clone")); - assertEquals(2, tableNames.size()); - - // --- Test Case 3: Get "VIEW" type --- - ResultSet rsView = databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"VIEW"}); - assertTrue(rsView.next()); - assertEquals("my_view", rsView.getString("TABLE_NAME")); - assertEquals("VIEW", rsView.getString("TABLE_TYPE")); - assertFalse(rsView.next()); - - // --- Test Case 4: Get "EXTERNAL TABLE" type --- - ResultSet rsExternal = - databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"EXTERNAL"}); - assertTrue(rsExternal.next()); - assertEquals("external_table", rsExternal.getString("TABLE_NAME")); - assertEquals("EXTERNAL", rsExternal.getString("TABLE_TYPE")); - assertFalse(rsExternal.next()); - - // --- Test Case 5: Get "MATERIALIZED_VIEW" type --- - ResultSet rsMaterialized = - databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"MATERIALIZED_VIEW"}); - assertTrue(rsMaterialized.next()); - assertEquals("my_materialized_view", rsMaterialized.getString("TABLE_NAME")); - assertEquals("MATERIALIZED_VIEW", rsMaterialized.getString("TABLE_TYPE")); - assertFalse(rsMaterialized.next()); - - // --- Test Case 6: Get "SNAPSHOT" type --- - ResultSet rsSnapshot = - databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"SNAPSHOT"}); - assertTrue(rsSnapshot.next()); - assertEquals("base_table_snapshot", rsSnapshot.getString("TABLE_NAME")); - assertEquals("SNAPSHOT", rsSnapshot.getString("TABLE_TYPE")); - assertFalse(rsSnapshot.next()); - - // --- Test Case 8: Get multiple types ("TABLE" and "VIEW") --- - ResultSet rsMulti = - databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"TABLE", "VIEW"}); - Set multiTableNames = new HashSet<>(); - while (rsMulti.next()) { - multiTableNames.add(rsMulti.getString("TABLE_NAME")); - } - assertTrue(multiTableNames.contains("base_table")); - assertTrue(multiTableNames.contains("base_table_clone")); - assertTrue(multiTableNames.contains("my_view")); - assertEquals(3, multiTableNames.size()); - - // --- Test Case 9: tableNamePattern --- - ResultSet rsNamePattern = databaseMetaData.getTables(PROJECT_ID, DATASET, "base%", null); - Set baseTableNames = new HashSet<>(); - while (rsNamePattern.next()) { - baseTableNames.add(rsNamePattern.getString("TABLE_NAME")); - } - assertTrue(baseTableNames.contains("base_table")); - assertTrue(baseTableNames.contains("base_table_clone")); - assertTrue(baseTableNames.contains("base_table_snapshot")); - assertEquals(3, baseTableNames.size()); - - // --- Test Case 10: No matching table --- - ResultSet rsNoMatch = - databaseMetaData.getTables(PROJECT_ID, DATASET, "nonexistent_table", null); - assertFalse(rsNoMatch.next()); - - // --- Test Case 11: Null type in array --- - ResultSet rsNullType = - databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {null, "VIEW"}); - assertTrue(rsNullType.next()); - assertEquals("VIEW", rsNullType.getString("TABLE_TYPE")); - assertEquals("my_view", rsNullType.getString("TABLE_NAME")); - assertFalse(rsNullType.next()); - } - - @Test - public void testDatabaseMetadataGetSchemas() throws SQLException { - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - - // Test case 1: Get all schemas with catalog and check for the presence of specific schemas - ResultSet rsAll = databaseMetaData.getSchemas(PROJECT_ID, null); - Set actualSchemas = new HashSet<>(); - while (rsAll.next()) { - assertEquals(PROJECT_ID, rsAll.getString("TABLE_CATALOG")); - actualSchemas.add(rsAll.getString("TABLE_SCHEM")); - } - assertTrue(actualSchemas.contains("JDBC_INTEGRATION_DATASET")); - assertTrue(actualSchemas.contains("JDBC_TABLE_TYPES_TEST")); - assertTrue(actualSchemas.contains("ODBC_TEST_DATASET")); - - // Test case 2: Get schemas with catalog and schemaPattern matching "JDBC_NIGHTLY_IT_DATASET" - ResultSet rsPattern = databaseMetaData.getSchemas(PROJECT_ID, "JDBC_NIGHTLY_IT_DATASET"); - Set actualSchemasPattern = new HashSet<>(); - while (rsPattern.next()) { - assertEquals(PROJECT_ID, rsPattern.getString("TABLE_CATALOG")); - actualSchemasPattern.add(rsPattern.getString("TABLE_SCHEM")); - } - assertTrue(actualSchemasPattern.contains("JDBC_NIGHTLY_IT_DATASET")); - assertEquals(1, actualSchemasPattern.size()); - - // Test case 3: Get schemas with catalog and schemaPattern matching "nonexistent" - ResultSet rsNoMatch = databaseMetaData.getSchemas(PROJECT_ID, "nonexistent"); - assertFalse(rsNoMatch.next()); - - // Test case 4: Get schemas with non-existent catalog - rsNoMatch = databaseMetaData.getSchemas("invalid-catalog", null); - assertFalse(rsNoMatch.next()); - } - - @Test - public void testDatabaseMetadataGetSchemasNoArgs() throws SQLException { - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - String expectedCatalog = bigQueryConnection.getCatalog(); - assertNotNull("Project ID (catalog) from connection should not be null", expectedCatalog); - - // Test case: Get all schemas (datasets) for the current project - try (ResultSet rsAll = databaseMetaData.getSchemas()) { - assertNotNull("ResultSet from getSchemas() should not be null", rsAll); - boolean foundTestDataset = false; - int rowCount = 0; - while (rsAll.next()) { - rowCount++; - assertEquals( - "TABLE_CATALOG should match the connection's project ID", - expectedCatalog, - rsAll.getString("TABLE_CATALOG")); - String schemaName = rsAll.getString("TABLE_SCHEM"); - assertNotNull("TABLE_SCHEM should not be null", schemaName); - if (DATASET.equals(schemaName) - || DATASET2.equals(schemaName) - || CONSTRAINTS_DATASET.equals(schemaName) - || "JDBC_TABLE_TYPES_TEST".equals(schemaName) - || "JDBC_INTEGRATION_DATASET".equals(schemaName)) { - foundTestDataset = true; - } - } - assertTrue("At least one of the known test datasets should be found", foundTestDataset); - assertTrue("Should retrieve at least one schema/dataset", rowCount > 0); - } - } - - @Test - public void testDatabaseMetaDataGetFunctions() throws SQLException { - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - String testSchema = "JDBC_TABLE_TYPES_TEST"; - String testCatalog = PROJECT_ID; - - Set expectedFunctionNames = - new HashSet<>( - Arrays.asList( - "complex_scalar_sql_udf", - "persistent_sql_udf_named_params", - "scalar_js_udf", - "scalar_sql_udf")); - - // Test 1: Get all functions from a specific schema - ResultSet rsAll = databaseMetaData.getFunctions(testCatalog, testSchema, null); - Set foundFunctionNames = new HashSet<>(); - int countAll = 0; - while (rsAll.next()) { - countAll++; - assertEquals(testCatalog, rsAll.getString("FUNCTION_CAT")); - assertEquals(testSchema, rsAll.getString("FUNCTION_SCHEM")); - String funcName = rsAll.getString("FUNCTION_NAME"); - foundFunctionNames.add(funcName); - assertNull(rsAll.getString("REMARKS")); - assertEquals(DatabaseMetaData.functionResultUnknown, rsAll.getShort("FUNCTION_TYPE")); - assertEquals(funcName, rsAll.getString("SPECIFIC_NAME")); - } - assertEquals( - "Should find all " + expectedFunctionNames.size() + " functions in " + testSchema, - expectedFunctionNames.size(), - countAll); - assertEquals(expectedFunctionNames, foundFunctionNames); - rsAll.close(); - - // Test 2: Get a specific function using functionNamePattern - String specificFunctionName = "scalar_sql_udf"; - ResultSet rsSpecific = - databaseMetaData.getFunctions(testCatalog, testSchema, specificFunctionName); - assertTrue("Should find the specific function " + specificFunctionName, rsSpecific.next()); - assertEquals(testCatalog, rsSpecific.getString("FUNCTION_CAT")); - assertEquals(testSchema, rsSpecific.getString("FUNCTION_SCHEM")); - assertEquals(specificFunctionName, rsSpecific.getString("FUNCTION_NAME")); - assertNull(rsSpecific.getString("REMARKS")); - assertEquals(DatabaseMetaData.functionResultUnknown, rsSpecific.getShort("FUNCTION_TYPE")); - assertEquals(specificFunctionName, rsSpecific.getString("SPECIFIC_NAME")); - assertFalse("Should only find one row for exact function match", rsSpecific.next()); - rsSpecific.close(); - - // Test 3: Get functions using a wildcard functionNamePattern "scalar%" - // Expected order due to sorting: scalar_js_udf, scalar_sql_udf - ResultSet rsWildcard = databaseMetaData.getFunctions(testCatalog, testSchema, "scalar%"); - assertTrue("Should find functions matching 'scalar%'", rsWildcard.next()); - assertEquals("scalar_js_udf", rsWildcard.getString("FUNCTION_NAME")); - assertEquals(DatabaseMetaData.functionResultUnknown, rsWildcard.getShort("FUNCTION_TYPE")); - - assertTrue("Should find the second function matching 'scalar%'", rsWildcard.next()); - assertEquals("scalar_sql_udf", rsWildcard.getString("FUNCTION_NAME")); - assertEquals(DatabaseMetaData.functionResultUnknown, rsWildcard.getShort("FUNCTION_TYPE")); - assertFalse("Should be no more functions matching 'scalar%'", rsWildcard.next()); - rsWildcard.close(); - - // Test 4: Schema pattern with wildcard - ResultSet rsSchemaWildcard = - databaseMetaData.getFunctions(testCatalog, "JDBC_TABLE_TYPES_T%", "complex_scalar_sql_udf"); - assertTrue("Should find function with schema wildcard", rsSchemaWildcard.next()); - assertEquals(testSchema, rsSchemaWildcard.getString("FUNCTION_SCHEM")); - assertEquals("complex_scalar_sql_udf", rsSchemaWildcard.getString("FUNCTION_NAME")); - assertFalse( - "Should only find one row for this schema wildcard and specific function", - rsSchemaWildcard.next()); - rsSchemaWildcard.close(); - - // Test 5: Non-existent function - ResultSet rsNonExistentFunc = - databaseMetaData.getFunctions(testCatalog, testSchema, "non_existent_function_xyz123"); - assertFalse("Should not find a non-existent function", rsNonExistentFunc.next()); - rsNonExistentFunc.close(); - - // Test 6: Non-existent schema - ResultSet rsNonExistentSchema = - databaseMetaData.getFunctions(testCatalog, "NON_EXISTENT_SCHEMA_XYZ123", null); - assertFalse("Should not find functions in a non-existent schema", rsNonExistentSchema.next()); - rsNonExistentSchema.close(); - - // Test 7: Empty schema pattern - ResultSet rsEmptySchema = databaseMetaData.getFunctions(testCatalog, "", null); - assertFalse("Empty schema pattern should return no results", rsEmptySchema.next()); - rsEmptySchema.close(); - - // Test 8: Empty function name pattern - ResultSet rsEmptyFunction = databaseMetaData.getFunctions(testCatalog, testSchema, ""); - assertFalse("Empty function name pattern should return no results", rsEmptyFunction.next()); - rsEmptyFunction.close(); - - // Test 9: Null catalog - ResultSet rsNullCatalog = databaseMetaData.getFunctions(null, testSchema, null); - assertFalse("Null catalog should return no results", rsNullCatalog.next()); - rsNullCatalog.close(); - } - - @Test - public void testDatabaseMetadataGetFunctionColumns() throws SQLException { - DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); - String testCatalog = PROJECT_ID; - String testSchema = "JDBC_TABLE_TYPES_TEST"; - - // Test Case 1: Specific function 'scalar_sql_udf', specific column 'x' - String specificFunction1 = "scalar_sql_udf"; - String specificColumn1 = "x"; - ResultSet rs = - databaseMetaData.getFunctionColumns( - testCatalog, testSchema, specificFunction1, specificColumn1); - - assertTrue("Should find column 'x' for function 'scalar_sql_udf'", rs.next()); - assertEquals(testCatalog, rs.getString("FUNCTION_CAT")); - assertEquals(testSchema, rs.getString("FUNCTION_SCHEM")); - assertEquals(specificFunction1, rs.getString("FUNCTION_NAME")); - assertEquals(specificColumn1, rs.getString("COLUMN_NAME")); - assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); - assertEquals(Types.BIGINT, rs.getInt("DATA_TYPE")); - assertEquals("BIGINT", rs.getString("TYPE_NAME")); - assertEquals(19, rs.getInt("PRECISION")); - assertEquals(null, rs.getObject("LENGTH")); - assertTrue(rs.wasNull()); - assertEquals(0, rs.getShort("SCALE")); - assertEquals(10, rs.getShort("RADIX")); - assertEquals(DatabaseMetaData.functionNullableUnknown, rs.getShort("NULLABLE")); - assertNull(rs.getString("REMARKS")); - assertEquals(null, rs.getObject("CHAR_OCTET_LENGTH")); - assertTrue(rs.wasNull()); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertEquals("", rs.getString("IS_NULLABLE")); - assertEquals(specificFunction1, rs.getString("SPECIFIC_NAME")); - assertFalse("Should only find one row for exact column match", rs.next()); - rs.close(); - - // Test Case 2: Specific function 'complex_scalar_sql_udf', specific column 'arr' - String specificFunction2 = "complex_scalar_sql_udf"; - String specificColumn2 = "arr"; - rs = - databaseMetaData.getFunctionColumns( - testCatalog, testSchema, specificFunction2, specificColumn2); - assertTrue("Should find column 'arr' for function 'complex_scalar_sql_udf'", rs.next()); - assertEquals(testCatalog, rs.getString("FUNCTION_CAT")); - assertEquals(testSchema, rs.getString("FUNCTION_SCHEM")); - assertEquals(specificFunction2, rs.getString("FUNCTION_NAME")); - assertEquals(specificColumn2, rs.getString("COLUMN_NAME")); - assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); - assertEquals(Types.ARRAY, rs.getInt("DATA_TYPE")); - assertEquals("ARRAY", rs.getString("TYPE_NAME")); - assertEquals(null, rs.getObject("PRECISION")); - assertTrue(rs.wasNull()); - assertEquals(null, rs.getObject("LENGTH")); - assertTrue(rs.wasNull()); - assertEquals(null, rs.getObject("SCALE")); - assertTrue(rs.wasNull()); - assertEquals(null, rs.getObject("RADIX")); - assertTrue(rs.wasNull()); - assertEquals(DatabaseMetaData.functionNullableUnknown, rs.getShort("NULLABLE")); - assertNull(rs.getString("REMARKS")); - assertEquals(null, rs.getObject("CHAR_OCTET_LENGTH")); - assertTrue(rs.wasNull()); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertEquals("", rs.getString("IS_NULLABLE")); - assertEquals(specificFunction2, rs.getString("SPECIFIC_NAME")); - assertFalse("Should only find one row for exact column match", rs.next()); - rs.close(); - - // Test Case 3: All columns for 'persistent_sql_udf_named_params' (sorted by ordinal position) - String specificFunction3 = "persistent_sql_udf_named_params"; - rs = databaseMetaData.getFunctionColumns(testCatalog, testSchema, specificFunction3, null); - assertTrue("Should find columns for " + specificFunction3, rs.next()); - assertEquals(specificFunction3, rs.getString("FUNCTION_NAME")); - assertEquals("value1", rs.getString("COLUMN_NAME")); // Ordinal Position 1 - assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); - assertEquals(Types.BIGINT, rs.getInt("DATA_TYPE")); - assertEquals("BIGINT", rs.getString("TYPE_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - - assertTrue("Should find second column for " + specificFunction3, rs.next()); - assertEquals(specificFunction3, rs.getString("FUNCTION_NAME")); - assertEquals("value-two", rs.getString("COLUMN_NAME")); // Ordinal Position 2 - assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); - assertEquals(Types.NVARCHAR, rs.getInt("DATA_TYPE")); - assertEquals("NVARCHAR", rs.getString("TYPE_NAME")); - assertEquals(2, rs.getInt("ORDINAL_POSITION")); - assertFalse("Should be no more columns for " + specificFunction3, rs.next()); - rs.close(); - - // Test Case 4: Wildcard for function name "scalar%", specific column name "x" - rs = databaseMetaData.getFunctionColumns(testCatalog, testSchema, "scalar%", "x"); - assertTrue("Should find column 'x' for functions matching 'scalar%'", rs.next()); - assertEquals("scalar_sql_udf", rs.getString("FUNCTION_NAME")); - assertEquals("x", rs.getString("COLUMN_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertFalse("Should be no more columns named 'x' for functions matching 'scalar%'", rs.next()); - rs.close(); - - // Test Case 5: Wildcard for column name "%" for 'scalar_js_udf' - String specificFunction4 = "scalar_js_udf"; - rs = databaseMetaData.getFunctionColumns(testCatalog, testSchema, specificFunction4, "%"); - assertTrue("Should find columns for " + specificFunction4 + " with wildcard", rs.next()); - assertEquals(specificFunction4, rs.getString("FUNCTION_NAME")); - assertEquals("name", rs.getString("COLUMN_NAME")); // Ordinal Position 1 - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - - assertTrue("Should find second column for " + specificFunction4 + " with wildcard", rs.next()); - assertEquals(specificFunction4, rs.getString("FUNCTION_NAME")); - assertEquals("age", rs.getString("COLUMN_NAME")); // Ordinal Position 2 - assertEquals(2, rs.getInt("ORDINAL_POSITION")); - assertFalse("Should be no more columns for " + specificFunction4 + " with wildcard", rs.next()); - rs.close(); - - // Test Case 6: Non-existent function - rs = - databaseMetaData.getFunctionColumns( - testCatalog, testSchema, "non_existent_function_xyz", null); - assertFalse("Should not find columns for a non-existent function", rs.next()); - rs.close(); - } - - @Test - public void testRangeDataTypeWithJsonResultSet() throws SQLException { - String RANGE_DATA_TABLE = "JDBC_RANGE_DATA_TEST_TABLE_" + random.nextInt(99); - String range_date_literal = "RANGE '[2020-01-01, 2020-01-31)'"; - String range_datetime_literal = "RANGE '[2020-01-01 12:00:00, 2020-01-31 12:00:00)'"; - String range_timestamp_literal = - "RANGE '[2020-01-01 12:00:00+08, 2020-01-31 12:00:00+08)'"; - - String createRangeTable = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `range_date` RANGE," - + " `range_date_time` RANGE, `range_timestamp` RANGE);", - DATASET, RANGE_DATA_TABLE); - String insertQuery = - String.format( - "INSERT INTO %s.%s (id, range_date, range_date_time, range_timestamp) VALUES (1, %s," - + " %s, %s);", - DATASET, - RANGE_DATA_TABLE, - range_date_literal, - range_datetime_literal, - range_timestamp_literal); - String selectQuery = - String.format( - "SELECT id, range_date, range_date_time, range_timestamp FROM %s.%s WHERE id = 1;", - DATASET, RANGE_DATA_TABLE); - - boolean status = bigQueryStatement.execute(createRangeTable); - assertFalse(status); - - status = bigQueryStatement.execute(insertQuery); - assertFalse(status); - - ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); - - Integer numRows = 0; - String actual_range_date = ""; - String actual_range_datetime = ""; - String actual_range_timestamp = ""; - - while (resultSet.next()) { - numRows++; - actual_range_date = resultSet.getString("range_date"); - actual_range_datetime = resultSet.getString("range_date_time"); - actual_range_timestamp = resultSet.getString("range_timestamp"); - } - - String expected_range_date = "[2020-01-01, 2020-01-31)"; - String expected_range_datetime = "[2020-01-01T12:00:00, 2020-01-31T12:00:00)"; - String expected_range_timestamp = "[1577851200.000000, 1580443200.000000)"; - - assertThat(numRows).isEqualTo(1); - assertThat(actual_range_date).isEqualTo(expected_range_date); - assertThat(actual_range_datetime).isEqualTo(expected_range_datetime); - assertThat(actual_range_timestamp).isEqualTo(expected_range_timestamp); - - bigQueryStatement.execute( - String.format("DROP TABLE IF EXISTS %S.%s", DATASET, RANGE_DATA_TABLE)); - } - - @Test - public void testRangeDataTypeWithArrowResultSet() throws SQLException { - String selectQuery = - "select * from `DATATYPERANGETEST.RangeIntervalTestTable` order by intColumn limit 5000;"; - - String connection_uri = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" - + "OAuthType=3;ProjectId=" - + PROJECT_ID - + ";MaxResults=500;HighThroughputActivationRatio=1;" - + "HighThroughputMinTableSize=100;" - + "EnableHighThroughputAPI=1;JobCreationMode=1;"; - - // Read data via JDBC - Connection connection = DriverManager.getConnection(connection_uri); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery(selectQuery); - assertTrue(resultSet.getClass().getName().contains("BigQueryArrowResultSet")); - resultSet.next(); - assertEquals("[2024-07-14, 2024-09-23)", resultSet.getString("rangeField")); - connection.close(); - } - - @Test - public void testPrepareCallSql() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc"); - assertNotNull(callableStatement); - callableStatement.close(); - } - - @Test - public void testRegisterOutParamIndex() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - callableStatement.registerOutParameter(1, Types.VARCHAR); - callableStatement.close(); - } - - @Test - public void testRegisterOutParamName() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - callableStatement.registerOutParameter("ParamKey", Types.VARCHAR); - callableStatement.close(); - } - - @Test - public void testRegisterOutParamIndexScale() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - callableStatement.registerOutParameter(1, Types.NUMERIC, 2); - callableStatement.close(); - } - - @Test - public void testRegisterOutParamNameScale() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - callableStatement.registerOutParameter("ParamKey", Types.NUMERIC, 2); - callableStatement.close(); - } - - @Test - public void testPrepareCallSqlResultSetTypeConcurrency() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - assertNotNull(callableStatement); - callableStatement.close(); - } - - @Test - public void testPrepareCallConcurrencyRegisterOutParamIndex() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - assertNotNull(callableStatement); - callableStatement.registerOutParameter(1, Types.VARCHAR); - callableStatement.close(); - } - - @Test - public void testPrepareCallConcurrencyRegisterOutParamName() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - assertNotNull(callableStatement); - callableStatement.registerOutParameter("ParamKey", Types.VARCHAR); - callableStatement.close(); - } - - @Test - public void testPrepareCallConcurrencyRegisterOutParamIndexScale() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - assertNotNull(callableStatement); - callableStatement.registerOutParameter(1, Types.NUMERIC, 2); - callableStatement.close(); - } - - @Test - public void testPrepareCallConcurrencyRegisterOutParamNameScale() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - assertNotNull(callableStatement); - callableStatement.registerOutParameter("ParamKey", Types.NUMERIC, 2); - callableStatement.close(); - } - - @Test - public void testPrepareCallSqlResultSetTypeConcurrencyHoldability() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc", - ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, - ResultSet.CLOSE_CURSORS_AT_COMMIT); - assertNotNull(callableStatement); - callableStatement.close(); - } - - @Test - public void testPrepareCallHoldabilityRegisterOutParamIndex() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", - ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, - ResultSet.CLOSE_CURSORS_AT_COMMIT); - assertNotNull(callableStatement); - callableStatement.registerOutParameter(1, Types.VARCHAR); - callableStatement.close(); - } - - @Test - public void testPrepareCallHoldabilityRegisterOutParamName() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", - ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, - ResultSet.CLOSE_CURSORS_AT_COMMIT); - assertNotNull(callableStatement); - callableStatement.registerOutParameter("ParamKey", Types.VARCHAR); - callableStatement.close(); - } - - @Test - public void testPrepareCallHoldabilityRegisterOutParamIndexScale() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", - ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, - ResultSet.CLOSE_CURSORS_AT_COMMIT); - assertNotNull(callableStatement); - callableStatement.close(); - } - - @Test - public void testPrepareCallHoldabilityRegisterOutParamNameScale() throws SQLException { - CallableStatement callableStatement = - this.bigQueryConnection.prepareCall( - "call testProc('?')", - ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, - ResultSet.CLOSE_CURSORS_AT_COMMIT); - assertNotNull(callableStatement); - callableStatement.registerOutParameter("ParamKey", Types.NUMERIC, 2); - callableStatement.close(); - } - - @Test - public void testPrepareCallFailureResultSetType() throws SQLException { - assertThrows( - BigQueryJdbcSqlFeatureNotSupportedException.class, - () -> - this.bigQueryConnection.prepareCall( - "call testProc", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY)); - } - - @Test - public void testPrepareCallFailureResultSetConcurrency() throws SQLException { - assertThrows( - BigQueryJdbcSqlFeatureNotSupportedException.class, - () -> - this.bigQueryConnection.prepareCall( - "call testProc", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)); - } - - @Test - public void testPrepareCallFailureResultSetHoldability() throws SQLException { - assertThrows( - BigQueryJdbcSqlFeatureNotSupportedException.class, - () -> - this.bigQueryConnection.prepareCall( - "call testProc", - ResultSet.TYPE_FORWARD_ONLY, - ResultSet.CONCUR_READ_ONLY, - ResultSet.HOLD_CURSORS_OVER_COMMIT)); - } - - // Integration tests for CallableStatement Setters and Getters - @Test - public void testSetterGetterBigDecimal() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - BigDecimal expected = new BigDecimal(12344); - callableStatement.setBigDecimal(CALLABLE_STMT_PARAM_KEY, expected); - BigDecimal actual = callableStatement.getBigDecimal(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterBoolean() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Boolean expected = true; - callableStatement.setBoolean(CALLABLE_STMT_PARAM_KEY, expected); - Boolean actual = callableStatement.getBoolean(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterByte() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Byte expected = "hello".getBytes()[0]; - callableStatement.setByte(CALLABLE_STMT_PARAM_KEY, expected); - Byte actual = callableStatement.getByte(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterBytes() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - byte[] expected = "hello".getBytes(); - callableStatement.setBytes(CALLABLE_STMT_PARAM_KEY, expected); - byte[] actual = callableStatement.getBytes(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterDate() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Date expected = new Date(1234567); - callableStatement.setDate(CALLABLE_STMT_PARAM_KEY, expected); - Date actual = callableStatement.getDate(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterDateCal() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Date expected = new Date(1L); - Calendar cal = Calendar.getInstance(); - callableStatement.setDate(CALLABLE_STMT_PARAM_KEY, expected, cal); - Date actual = callableStatement.getDate(CALLABLE_STMT_PARAM_KEY, cal); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterDouble() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Double expected = 123.2345; - callableStatement.setDouble(CALLABLE_STMT_PARAM_KEY, expected); - Double actual = callableStatement.getDouble(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterFloat() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Float expected = 123.2345F; - callableStatement.setFloat(CALLABLE_STMT_PARAM_KEY, expected); - Float actual = callableStatement.getFloat(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterInt() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Integer expected = 123; - callableStatement.setInt(CALLABLE_STMT_PARAM_KEY, expected); - Integer actual = callableStatement.getInt(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterLong() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Long expected = 123L; - callableStatement.setLong(CALLABLE_STMT_PARAM_KEY, expected); - Long actual = callableStatement.getLong(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterNString() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - String expected = "heelo"; - callableStatement.setNString(CALLABLE_STMT_PARAM_KEY, expected); - String actual = callableStatement.getNString(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterObject() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - String expected = "heelo"; - callableStatement.setObject(CALLABLE_STMT_PARAM_KEY, expected); - Object actual = callableStatement.getObject(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterObjectWithSQLType() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - String expected = "heelo"; - callableStatement.setObject(CALLABLE_STMT_PARAM_KEY, expected, Types.NVARCHAR); - Object actual = callableStatement.getObject(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterObjectWithSqlTypeAndScale() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - String expected = "heelo"; - callableStatement.setObject(CALLABLE_STMT_PARAM_KEY, expected, Types.NVARCHAR, 0); - Object actual = callableStatement.getObject(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterString() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - String expected = "123"; - callableStatement.setString(CALLABLE_STMT_PARAM_KEY, expected); - String actual = callableStatement.getString(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterTime() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Time expected = new Time(1234567); - callableStatement.setTime(CALLABLE_STMT_PARAM_KEY, expected); - Time actual = callableStatement.getTime(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterTimeCal() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Time expected = new Time(1L); - Calendar cal = Calendar.getInstance(); - callableStatement.setTime(CALLABLE_STMT_PARAM_KEY, expected, cal); - Time actual = callableStatement.getTime(CALLABLE_STMT_PARAM_KEY, cal); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterTimestamp() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Timestamp expected = new Timestamp(1234567); - callableStatement.setTimestamp(CALLABLE_STMT_PARAM_KEY, expected); - Timestamp actual = callableStatement.getTimestamp(CALLABLE_STMT_PARAM_KEY); - assertEquals(expected, actual); - } - - @Test - public void testSetterGetterTimestampCal() throws SQLException { - CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); - assertNotNull(callableStatement); - Timestamp expected = new Timestamp(1L); - Calendar cal = Calendar.getInstance(); - callableStatement.setTimestamp(CALLABLE_STMT_PARAM_KEY, expected, cal); - Timestamp actual = callableStatement.getTimestamp(CALLABLE_STMT_PARAM_KEY, cal); - assertEquals(expected, actual); - } - - @Test - public void testPooledConnectionDataSourceSuccess() throws SQLException { - String connectionUrl = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;OAuthType=3;ProjectId=testProject;ConnectionPoolSize=20;ListenerPoolSize=20;"; - - PooledConnectionDataSource pooledDataSource = new PooledConnectionDataSource(); - pooledDataSource.setURL(connectionUrl); + PooledConnectionDataSource pooledDataSource = new PooledConnectionDataSource(); + pooledDataSource.setURL(connectionUrl); PooledConnection pooledConnection = pooledDataSource.getPooledConnection(); assertNotNull(pooledConnection); @@ -3332,254 +1757,6 @@ private void assertConnectionPoolingResults(String connectionURL, Long connectio pooledConnection.close(); } - @Test - public void testAdditionalProjectsInMetadata() throws SQLException { - String additionalProjectsValue = "bigquery-public-data"; - String datasetInAdditionalProject = "baseball"; - - String urlWithAdditionalProjects = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=" - + PROJECT_ID - + ";OAuthType=3" - + ";AdditionalProjects=" - + additionalProjectsValue; - - try (Connection conn = DriverManager.getConnection(urlWithAdditionalProjects)) { - DatabaseMetaData dbMetaData = conn.getMetaData(); - - // 1. Test getCatalogs() - Set foundCatalogs = new HashSet<>(); - try (ResultSet catalogsRs = dbMetaData.getCatalogs()) { - while (catalogsRs.next()) { - foundCatalogs.add(catalogsRs.getString("TABLE_CAT")); - } - } - assertTrue( - "getCatalogs() should contain the primary project ID", - foundCatalogs.contains(PROJECT_ID)); - assertTrue( - "getCatalogs() should contain the additional project ID", - foundCatalogs.contains(additionalProjectsValue)); - - // 2. Test getSchemas() - Set catalogsForSchemasFromAll = new HashSet<>(); - boolean foundAdditionalDataset = false; - try (ResultSet schemasRs = dbMetaData.getSchemas()) { - while (schemasRs.next()) { - String schemaName = schemasRs.getString("TABLE_SCHEM"); - String catalogName = schemasRs.getString("TABLE_CATALOG"); - catalogsForSchemasFromAll.add(catalogName); - if (additionalProjectsValue.equals(catalogName) - && datasetInAdditionalProject.equals(schemaName)) { - foundAdditionalDataset = true; - } - } - } - assertTrue( - "getSchemas() should list datasets from the primary project", - catalogsForSchemasFromAll.contains(PROJECT_ID)); - assertTrue( - "getSchemas() should list datasets from the additional project", - catalogsForSchemasFromAll.contains(additionalProjectsValue)); - assertTrue( - "Known dataset from additional project not found in getSchemas()", - foundAdditionalDataset); - - } catch (SQLException e) { - System.err.println("SQL Error during AdditionalProjects test: " + e.getMessage()); - throw e; - } - } - - @Test - public void testFilterTablesOnDefaultDataset_getTables() throws SQLException { - String defaultDatasetValue = CONSTRAINTS_DATASET; - String table1InDefaultDataset = CONSTRAINTS_TABLE_NAME; - String table2InDefaultDataset = CONSTRAINTS_TABLE_NAME2; - - String specificDatasetValue = "JDBC_TABLE_TYPES_TEST"; - String table1InSpecificDataset = "base_table"; - String table2InSpecificDataset = "external_table"; - - String connectionUrl = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=" - + PROJECT_ID - + ";OAuthType=3" - + ";DefaultDataset=" - + defaultDatasetValue - + ";FilterTablesOnDefaultDataset=1"; - try (Connection conn = DriverManager.getConnection(connectionUrl)) { - DatabaseMetaData dbMetaData = conn.getMetaData(); - - // Case 1: Catalog and schemaPattern are null/wildcard, should use DefaultDataset - try (ResultSet rs = dbMetaData.getTables(null, null, null, null)) { - Set tableNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); - tableNames.add(rs.getString("TABLE_NAME")); - } - assertTrue(tableNames.contains(table1InDefaultDataset)); - assertTrue(tableNames.contains(table2InDefaultDataset)); - } - - // Case 2: Explicit schemaPattern overrides DefaultDataset - try (ResultSet rs = dbMetaData.getTables(null, specificDatasetValue, null, null)) { - Set tableNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); - tableNames.add(rs.getString("TABLE_NAME")); - } - assertTrue(tableNames.contains(table1InSpecificDataset)); - assertTrue(tableNames.contains(table2InSpecificDataset)); - } - - // Case 3: Explicit catalog, schemaPattern is null/wildcard, should use DefaultDataset within - // that catalog - try (ResultSet rs = dbMetaData.getTables(PROJECT_ID, null, null, null)) { - Set tableNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); - tableNames.add(rs.getString("TABLE_NAME")); - } - assertTrue(tableNames.contains(table1InDefaultDataset)); - assertTrue(tableNames.contains(table2InDefaultDataset)); - } - - // Case 4: Explicit catalog and schemaPattern override DefaultDataset - try (ResultSet rs = dbMetaData.getTables(PROJECT_ID, specificDatasetValue, null, null)) { - Set tableNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); - tableNames.add(rs.getString("TABLE_NAME")); - } - assertTrue(tableNames.contains(table1InSpecificDataset)); - assertTrue(tableNames.contains(table2InSpecificDataset)); - } - } - } - - @Test - public void testFilterTablesOnDefaultDataset_getColumns() throws SQLException { - String defaultDatasetValue = CONSTRAINTS_DATASET; - String tableInDefaultDataset = CONSTRAINTS_TABLE_NAME; - String[] columnsInDefaultTable = {"id", "name", "second_name", "address"}; - - String specificDatasetValue = "JDBC_TABLE_TYPES_TEST"; - String tableInSpecificDataset = "base_table"; - String[] columnsInSpecificTable = {"id", "name", "created_at"}; - - String connectionUrl = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=" - + PROJECT_ID - + ";OAuthType=3" - + ";DefaultDataset=" - + defaultDatasetValue - + ";FilterTablesOnDefaultDataset=1"; - - try (Connection conn = DriverManager.getConnection(connectionUrl)) { - DatabaseMetaData dbMetaData = conn.getMetaData(); - - // Case 1: Catalog and schemaPattern are null/wildcard, should use DefaultDataset - try (ResultSet rs = dbMetaData.getColumns(null, null, tableInDefaultDataset, null)) { - Set columnNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); - assertEquals(tableInDefaultDataset, rs.getString("TABLE_NAME")); - columnNames.add(rs.getString("COLUMN_NAME")); - } - for (String expectedCol : columnsInDefaultTable) { - assertTrue(columnNames.contains(expectedCol)); - } - assertEquals(columnsInDefaultTable.length, columnNames.size()); - } - - // Case 2: Explicit schemaPattern overrides DefaultDataset - try (ResultSet rs = - dbMetaData.getColumns(null, specificDatasetValue, tableInSpecificDataset, null)) { - Set columnNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); - assertEquals(tableInSpecificDataset, rs.getString("TABLE_NAME")); - columnNames.add(rs.getString("COLUMN_NAME")); - } - for (String expectedCol : columnsInSpecificTable) { - assertTrue(columnNames.contains(expectedCol)); - } - assertEquals(columnsInSpecificTable.length, columnNames.size()); - } - - // Case 3: Explicit catalog, schemaPattern is null/wildcard, should use DefaultDataset within - // that catalog - try (ResultSet rs = dbMetaData.getColumns(PROJECT_ID, null, tableInDefaultDataset, null)) { - Set columnNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); - assertEquals(tableInDefaultDataset, rs.getString("TABLE_NAME")); - columnNames.add(rs.getString("COLUMN_NAME")); - } - for (String expectedCol : columnsInDefaultTable) { - assertTrue(columnNames.contains(expectedCol)); - } - assertEquals(columnsInDefaultTable.length, columnNames.size()); - } - - // Case 4: Explicit catalog and schemaPattern override DefaultDataset - try (ResultSet rs = - dbMetaData.getColumns(PROJECT_ID, specificDatasetValue, tableInSpecificDataset, null)) { - Set columnNames = new HashSet<>(); - while (rs.next()) { - assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); - assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); - assertEquals(tableInSpecificDataset, rs.getString("TABLE_NAME")); - columnNames.add(rs.getString("COLUMN_NAME")); - } - for (String expectedCol : columnsInSpecificTable) { - assertTrue(columnNames.contains(expectedCol)); - } - assertEquals(columnsInSpecificTable.length, columnNames.size()); - } - } - } - - @Test - public void testAlterTable() throws SQLException { - String TABLE_NAME = "JDBC_ALTER_TABLE_" + randomNumber; - String createQuery = - String.format("CREATE OR REPLACE TABLE %s.%s (`StringField` STRING);", DATASET, TABLE_NAME); - String addColumnQuery = - String.format("ALTER TABLE %s.%s ADD COLUMN `IntegerField` INTEGER;", DATASET, TABLE_NAME); - String dropColumnQuery = - String.format( - "UPDATE %s.%s SET StringField='Jane Doe' WHERE IntegerField=111", DATASET, TABLE_NAME); - String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); - String selectQuery = String.format("SELECT * FROM %s.%s", DATASET, TABLE_NAME); - - int createStatus = bigQueryStatement.executeUpdate(createQuery); - assertEquals(0, createStatus); - - int addColumnStatus = bigQueryStatement.executeUpdate(addColumnQuery); - assertEquals(0, addColumnStatus); - - bigQueryStatement.executeQuery(selectQuery); - int selectStatus = bigQueryStatement.getUpdateCount(); - assertEquals(-1, selectStatus); - - int dropColumnStatus = bigQueryStatement.executeUpdate(dropColumnQuery); - assertEquals(0, dropColumnStatus); - - int dropStatus = bigQueryStatement.executeUpdate(dropQuery); - assertEquals(0, dropStatus); - - bigQueryStatement.execute(String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TABLE_NAME)); - } - public void testQueryPropertyDataSetProjectIdQueriesToCorrectDataset() throws SQLException { String connection_uri = "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" @@ -3764,27 +1941,6 @@ public void testQueryPropertyServiceAccountFollowsIamPermission() throws SQLExce connection.close(); } - @Test - public void testValidLegacySQLStatement() throws SQLException { - String legacyJoinQuery = - "SELECT\n" - + " repo_name\n" - + "FROM\n" - + " [bigquery-public-data.github_repos.commits],\n" - + " [bigquery-public-data.github_repos.sample_commits] LIMIT 10"; - String connection_uri = - "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" - + "OAuthType=3;ProjectId=" - + PROJECT_ID - + ";QueryDialect=BIG_QUERY;"; - Connection connection = DriverManager.getConnection(connection_uri); - Statement statement = connection.createStatement(); - - boolean result = statement.execute(legacyJoinQuery); - assertTrue(result); - connection.close(); - } - @Test public void testMultipleTransactionsThrowsUnsupported() throws SQLException { BigQueryConnection connection = @@ -3795,64 +1951,6 @@ public void testMultipleTransactionsThrowsUnsupported() throws SQLException { connection.close(); } - @Test - public void testConnectionWithMultipleTransactionCommits() throws SQLException { - String TRANSACTION_TABLE = "JDBC_MULTI_COMMIT_TABLE" + randomNumber; - String createTransactionTable = - String.format( - "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);", - DATASET, TRANSACTION_TABLE); - String insertQuery = - String.format( - "INSERT INTO %s.%s (id, name, age) VALUES (12, 'DwightShrute', %s);", - DATASET, TRANSACTION_TABLE, randomNumber); - String updateQuery = - String.format( - "UPDATE %s.%s SET age = 14 WHERE age = %s;", DATASET, TRANSACTION_TABLE, randomNumber); - String selectQuery = - String.format("SELECT id, name, age FROM %s.%s WHERE id = 12;", DATASET, TRANSACTION_TABLE); - - bigQueryStatement.execute(createTransactionTable); - - Connection connection = DriverManager.getConnection(session_enabled_connection_uri); - connection.setAutoCommit(false); - - Statement statement = connection.createStatement(); - statement.execute(insertQuery); - statement.execute(updateQuery); - connection.commit(); // First transaction - - // After commit, a new transaction should have started. - // Executing another query and then rolling it back. - String insertQuery2 = - String.format( - "INSERT INTO %s.%s (id, name, age) VALUES (15, 'MichaelScott', 25);", - DATASET, TRANSACTION_TABLE); - statement.execute(insertQuery2); - connection.rollback(); // Second transaction - - // Verify state with the static bigQueryStatement - ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); - int count = 0; - while (resultSet.next()) { - count++; - assertEquals(14, resultSet.getInt("age")); - } - assertEquals(1, count); // Only first transaction should be committed. - - // Verify the second insert was rolled back - ResultSet rs2 = - bigQueryStatement.executeQuery( - String.format("SELECT * FROM %s.%s WHERE id=15", DATASET, TRANSACTION_TABLE)); - assertFalse(rs2.next()); - - bigQueryStatement.execute( - String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE)); - - statement.close(); - connection.close(); - } - // Private Helper functions private String getSessionId() throws InterruptedException { QueryJobConfiguration stubJobConfig = @@ -3863,247 +1961,6 @@ private String getSessionId() throws InterruptedException { return stubJob.getStatistics().getSessionInfo().getSessionId(); } - @Test - public void testCallableStatementScriptExecuteUpdate() throws SQLException { - int randomNum = java.util.UUID.randomUUID().hashCode(); - String insertName = "callable-statement-dml-insert-test"; - String insertResult = String.format("%s-%d", insertName, randomNum); - String updateName = "callable-statement-dml-update-test"; - String updateResult = String.format("%s-%d", updateName, randomNum); - String selectStmtQuery = - String.format("SELECT * FROM %s.%s WHERE id = ?", DATASET, CALLABLE_STMT_DML_TABLE_NAME); - String insertCallStmtQuery = - String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_INSERT_PROC_NAME); - String updateCallStmtQuery = - String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_UPDATE_PROC_NAME); - String deleteCallStmtQuery = - String.format("CALL %s.%s(?);", DATASET, CALLABLE_STMT_DML_DELETE_PROC_NAME); - - // DML INSERT - CallableStatement callableStatement = bigQueryConnection.prepareCall(insertCallStmtQuery); - assertNotNull(callableStatement); - callableStatement.setString(1, insertName); - callableStatement.setInt(2, randomNum); - callableStatement.setString(3, insertResult); - int rowsInserted = callableStatement.executeUpdate(); - assertEquals(1, rowsInserted); - - PreparedStatement preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); - assertNotNull(preparedStatement); - preparedStatement.setInt(1, randomNum); - ResultSet rs = preparedStatement.executeQuery(); - assertNotNull(rs); - assertTrue(rs.next()); - - assertEquals(insertName, rs.getString(1)); - assertEquals(randomNum, rs.getInt(2)); - assertEquals(insertResult, rs.getString(3)); - - // DML UPDATE - callableStatement = bigQueryConnection.prepareCall(updateCallStmtQuery); - assertNotNull(callableStatement); - callableStatement.setString(1, updateName); - callableStatement.setInt(2, randomNum); - callableStatement.setString(3, updateResult); - int rowsUpdated = callableStatement.executeUpdate(); - assertEquals(1, rowsUpdated); - - preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); - assertNotNull(preparedStatement); - preparedStatement.setInt(1, randomNum); - rs = preparedStatement.executeQuery(); - assertNotNull(rs); - assertTrue(rs.next()); - - assertEquals(updateName, rs.getString(1)); - assertEquals(randomNum, rs.getInt(2)); - assertEquals(updateResult, rs.getString(3)); - - // DML DELETE - callableStatement = bigQueryConnection.prepareCall(deleteCallStmtQuery); - assertNotNull(callableStatement); - callableStatement.setInt(1, randomNum); - int rowsDeleted = callableStatement.executeUpdate(); - assertEquals(1, rowsDeleted); - - preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); - assertNotNull(preparedStatement); - preparedStatement.setInt(1, randomNum); - rs = preparedStatement.executeQuery(); - assertNotNull(rs); - assertFalse(rs.next()); - - callableStatement.close(); - } - - @Test - public void testCallableStatementScriptExecuteLargeUpdate() throws SQLException { - int randomNum = java.util.UUID.randomUUID().hashCode(); - String insertName = "callable-statement-dml-insert-test"; - String insertResult = String.format("%s-%d", insertName, randomNum); - String updateName = "callable-statement-dml-update-test"; - String updateResult = String.format("%s-%d", updateName, randomNum); - String selectStmtQuery = - String.format("SELECT * FROM %s.%s WHERE id = ?", DATASET, CALLABLE_STMT_DML_TABLE_NAME); - String insertCallStmtQuery = - String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_INSERT_PROC_NAME); - String updateCallStmtQuery = - String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_UPDATE_PROC_NAME); - String deleteCallStmtQuery = - String.format("CALL %s.%s(?);", DATASET, CALLABLE_STMT_DML_DELETE_PROC_NAME); - - // DML INSERT - CallableStatement callableStatement = bigQueryConnection.prepareCall(insertCallStmtQuery); - assertNotNull(callableStatement); - callableStatement.setString(1, insertName); - callableStatement.setInt(2, randomNum); - callableStatement.setString(3, insertResult); - long rowsInserted = callableStatement.executeLargeUpdate(); - assertEquals(1L, rowsInserted); - - PreparedStatement preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); - assertNotNull(preparedStatement); - preparedStatement.setInt(1, randomNum); - ResultSet rs = preparedStatement.executeQuery(); - assertNotNull(rs); - assertTrue(rs.next()); - - assertEquals(insertName, rs.getString(1)); - assertEquals(randomNum, rs.getInt(2)); - assertEquals(insertResult, rs.getString(3)); - - // DML UPDATE - callableStatement = bigQueryConnection.prepareCall(updateCallStmtQuery); - assertNotNull(callableStatement); - callableStatement.setString(1, updateName); - callableStatement.setInt(2, randomNum); - callableStatement.setString(3, updateResult); - long rowsUpdated = callableStatement.executeLargeUpdate(); - assertEquals(1L, rowsUpdated); - - preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); - assertNotNull(preparedStatement); - preparedStatement.setInt(1, randomNum); - rs = preparedStatement.executeQuery(); - assertNotNull(rs); - assertTrue(rs.next()); - - assertEquals(updateName, rs.getString(1)); - assertEquals(randomNum, rs.getInt(2)); - assertEquals(updateResult, rs.getString(3)); - - // DML DELETE - callableStatement = bigQueryConnection.prepareCall(deleteCallStmtQuery); - assertNotNull(callableStatement); - callableStatement.setInt(1, randomNum); - long rowsDeleted = callableStatement.executeLargeUpdate(); - assertEquals(1L, rowsDeleted); - - preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); - assertNotNull(preparedStatement); - preparedStatement.setInt(1, randomNum); - rs = preparedStatement.executeQuery(); - assertNotNull(rs); - assertFalse(rs.next()); - - callableStatement.close(); - } - - @Test - public void testScript() throws SQLException { - String BASE_QUERY = - "SELECT * FROM bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2017 order by" - + " trip_distance asc LIMIT %s;"; - String query1 = String.format(BASE_QUERY, 5000); - String query2 = String.format(BASE_QUERY, 7000); - String query3 = String.format(BASE_QUERY, 9000); - - bigQueryStatement.execute(query1 + query2 + query3); - ResultSet resultSet = bigQueryStatement.getResultSet(); - assertEquals(5000, resultSetRowCount(resultSet)); - - boolean hasMoreResult = bigQueryStatement.getMoreResults(); - assertTrue(hasMoreResult); - resultSet = bigQueryStatement.getResultSet(); - assertEquals(7000, resultSetRowCount(resultSet)); - - hasMoreResult = bigQueryStatement.getMoreResults(); - assertTrue(hasMoreResult); - resultSet = bigQueryStatement.getResultSet(); - assertEquals(9000, resultSetRowCount(resultSet)); - } - - @Test - public void testCallableStatementScriptExecute() throws SQLException { - int randomNum = random.nextInt(99); - String callableStmtQuery = - String.format( - "DECLARE call_result STRING;" - + "CALL %s.%s(?,?,call_result);" - + "SELECT * FROM %s.%s WHERE result = call_result;", - DATASET, CALLABLE_STMT_PROC_NAME, DATASET, CALLABLE_STMT_TABLE_NAME); - CallableStatement callableStatement = bigQueryConnection.prepareCall(callableStmtQuery); - callableStatement.setString(1, "callable-stmt-test"); - callableStatement.setInt(2, randomNum); - - assertFalse(callableStatement.execute()); - assertEquals(1, callableStatement.getUpdateCount()); - - // This is an actual SELECT * from the above - assertTrue(callableStatement.getMoreResults()); - ResultSet resultSet = callableStatement.getResultSet(); - ResultSetMetaData rsMetadata = resultSet.getMetaData(); - assertEquals(3, rsMetadata.getColumnCount()); - - assertTrue(resultSet.next()); - - String expected = String.format("callable-stmt-test-%d", randomNum); - String actual = resultSet.getString(3); - - assertEquals(expected, actual); - - // Validate there are no more results - assertFalse(callableStatement.getMoreResults()); - assertEquals(-1, callableStatement.getUpdateCount()); - callableStatement.close(); - } - - @Test - public void testExecuteScriptWithExpession() throws SQLException { - int randomNum = random.nextInt(99); - String query = String.format("DECLARE x INT64; SET x = (SELECT %s); SELECT x;", randomNum); - - assertTrue(bigQueryStatement.execute(query)); - ResultSet rs = bigQueryStatement.getResultSet(); - assertTrue(rs.next()); - assertEquals(randomNum, rs.getInt(1)); - assertFalse(rs.next()); - assertFalse(bigQueryStatement.getMoreResults()); - assertEquals(-1, bigQueryStatement.getUpdateCount()); - } - - @Test - public void testInformationSchemaTables() throws SQLException { - String query = String.format("SELECT * FROM %s.INFORMATION_SCHEMA.TABLES", DATASET); - try (Statement statement = bigQueryConnection.createStatement(); - ResultSet resultSet = statement.executeQuery(query)) { - ResultSetMetaData metaData = resultSet.getMetaData(); - int columnCount = metaData.getColumnCount(); - assertTrue(columnCount > 0); - int rowCount = 0; - while (resultSet.next()) { - rowCount++; - for (int i = 1; i <= columnCount; i++) { - Object obj = resultSet.getObject(i); - if (obj != null) { - assertNotNull(obj.toString()); - } - } - } - assertTrue(rowCount > 0); - } - } - private void validate( String method, BiFunction getter, @@ -4150,285 +2007,6 @@ private void validate( } } - @Test - public void validateGetString() throws Exception { - final ImmutableMap stringResults = - new ImmutableMap.Builder() - .put("stringField", "StringValue") - .put("bytesField", "Qnl0ZXNWYWx1ZQ==") - .put("intField", "123") - .put("floatField", "10.5") - .put("numericField", "12345.67") - .put("bigNumericField", "98765432109876543210.123456789") - .put("booleanField", "true") - .put("timestampFiled", "2023-07-28 12:30:00.000000") - .put("dateField", "2023-07-28") - .put("timeField", "12:30:00.000") - .put("dateTimeField", "2023-07-28 12:30:00.000000") - .put("geographyField", "POINT(-74.006 40.7128)") - .put( - "recordField", - "{\"name\":\"NameValue\",\"recordNested\":{\"lastName\":\"LastNameValue\"}}") - .put("rangeField", "[2023-01-01, 2023-12-01)") - .put("jsonField", "{\"key\":\"value\"}") - .put("arrayString", "[abc, def, ghi]") - .put("arrayRecord", "[{\"value\":\"rec_val1\"}, {\"value\":\"rec_val2\"}]") - .put("arrayBytes", "[Ynl0ZTE=, Ynl0ZTI=]") - .put("arrayInteger", "[10, 20]") - .put("arrayNumeric", "[10.5, 20.5]") - .put("arrayBignumeric", "[100.1, 200.2]") - .put("arrayBoolean", "[true, false]") - .put("arrayTimestamp", "[2023-01-01 01:00:00.0, 2023-01-01 02:00:00.0]") - .put("arrayDate", "[2023-01-01, 2023-01-02]") - .put("arrayTime", "[01:00:00, 02:00:00]") - .put("arrayDatetime", "[2023-01-01 01:00:00.0, 2023-01-01 02:00:00.0]") - .put("arrayGeography", "[POINT(1 1), POINT(2 2)]") - .put("arrayRange", "[[2023-01-01, 2023-01-03), [2023-01-04, 2023-01-06)]") - .put("arrayJson", "[{\"a\":1}, {\"b\":2}]") - .put("arrayFloat", "[1.1, 2.2]") - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getString(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getString", getter, stringResults); - } - - @Test - public void validateGetInt() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("intField", 123) - .put("floatField", 10) - .put("numericField", 12345) - .put("booleanField", 1) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getInt(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getInt", getter, result); - } - - @Test - public void validateGetLong() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("intField", 123L) - .put("floatField", 10L) - .put("numericField", 12345L) - .put("booleanField", 1L) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getLong(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getLong", getter, result); - } - - @Test - public void validateGetBool() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("intField", true) - .put("floatField", true) - .put("numericField", true) - .put("booleanField", true) - .put("bigNumericField", true) - .put("stringField", false) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getBoolean(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getBool", getter, result); - } - - @Test - public void validateGetFloat() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("intField", (float) 123.0) - .put("floatField", (float) 10.5) - .put("numericField", (float) 12345.67) - .put("bigNumericField", (float) 98765432109876543210.123456789) - .put("booleanField", (float) 1.0) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getFloat(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getFloat", getter, result); - } - - @Test - public void validateGetDouble() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("intField", (double) 123.0) - .put("floatField", (double) 10.5) - .put("numericField", (double) 12345.67) - .put("bigNumericField", (double) 98765432109876543210.123456789) - .put("booleanField", (double) 1.0) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getDouble(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getDouble", getter, result); - } - - @Test - public void validateGetShort() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("intField", (short) 123) - .put("floatField", (short) 10) - .put("numericField", (short) 12345) - .put("booleanField", (short) 1) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getShort(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getShort", getter, result); - } - - @Test - public void validateGetTime() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("timeField", Time.valueOf("12:30:00")) - .put("dateTimeField", Time.valueOf("12:30:00")) - .put("timestampFiled", Time.valueOf("12:30:00")) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getTime(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getTime", getter, result); - } - - @Test - public void validateGetDate() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("dateField", Date.valueOf("2023-07-28")) - .put("dateTimeField", Date.valueOf("2023-07-28")) - .put("timestampFiled", Date.valueOf("2023-07-28")) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getDate(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getDate", getter, result); - } - - @Test - public void validateGetTimestamp() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("timeField", Timestamp.valueOf("1970-01-01 12:30:00")) - .put("dateField", Timestamp.valueOf("2023-07-28 00:00:00")) - .put("dateTimeField", Timestamp.valueOf("2023-07-28 12:30:00")) - .put("timestampFiled", Timestamp.valueOf("2023-07-28 12:30:00")) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getTimestamp(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getTimestamp", getter, result); - } - - @Test - public void validateGetByte() throws Exception { - final ImmutableMap result = - new ImmutableMap.Builder() - .put("intField", (byte) 123) - .put("booleanField", (byte) 1) - .put("floatField", (byte) 10) - .build(); - BiFunction getter = - (s, i) -> { - try { - return s.getByte(i); - } catch (Exception e) { - return EXCEPTION_REPLACEMENT; - } - }; - validate("getByte", getter, result); - } - - @Test - public void validateGetObjectNullValues() throws Exception { - try (Connection connection = DriverManager.getConnection(connection_uri); - Connection connectionHTAPI = - DriverManager.getConnection( - connection_uri - + ";HighThroughputMinTableSize=0;HighThroughputActivationRatio=0;EnableHighThroughputAPI=1;"); - Statement statement = connection.createStatement(); - Statement statementHTAPI = connectionHTAPI.createStatement()) { - - String query = - "SELECT * FROM INTEGRATION_TEST_FORMAT.all_bq_types WHERE stringField is null;"; - ResultSet resultSetRegular = statement.executeQuery(query); - ResultSet resultSetArrow = statementHTAPI.executeQuery(query); - resultSetRegular.next(); - resultSetArrow.next(); - - for (int i = 1; i <= resultSetRegular.getMetaData().getColumnCount(); i++) { - String columnName = resultSetRegular.getMetaData().getColumnName(i); - if (!columnName.contains("array")) { - assertNull(resultSetRegular.getObject(i)); - assertNull(resultSetArrow.getObject(i)); - } else { - assertEquals(resultSetRegular.getObject(i).toString(), "[]"); - assertEquals(resultSetArrow.getObject(i).toString(), "[]"); - } - } - } - } - private int resultSetRowCount(ResultSet resultSet) throws SQLException { int rowCount = 0; while (resultSet.next()) { diff --git a/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDriverAgnosticTest.java b/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDriverAgnosticTest.java new file mode 100644 index 0000000000..3222acdac0 --- /dev/null +++ b/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDriverAgnosticTest.java @@ -0,0 +1,2642 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery.jdbc.it; + +import static com.google.common.truth.Truth.assertThat; +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.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.Job; +import com.google.cloud.bigquery.JobInfo; +import com.google.cloud.bigquery.QueryJobConfiguration; +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Instant; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashSet; +import java.util.Properties; +import java.util.Random; +import java.util.Set; +import java.util.function.BiFunction; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +public class ITDriverAgnosticTest extends ITBase { + static final String PROJECT_ID = ServiceOptions.getDefaultProjectId(); + static Connection bigQueryConnection; + static BigQuery bigQuery; + static Statement bigQueryStatement; + static Connection bigQueryConnectionNoReadApi; + static Statement bigQueryStatementNoReadApi; + static final String connection_uri = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID=" + + PROJECT_ID + + ";OAUTHTYPE=3"; + static final String session_enabled_connection_uri = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID=" + + PROJECT_ID + + ";OAUTHTYPE=3;EnableSession=1"; + private static final String BASE_QUERY = + "SELECT * FROM bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2017 order by" + + " trip_distance asc LIMIT %s"; + private static final Random random = new Random(); + private static final int randomNumber = random.nextInt(9999); + private static final String DATASET = "JDBC_PRESUBMIT_INTEGRATION_DATASET"; + private static final String DATASET2 = "JDBC_PRESUBMIT_INTEGRATION_DATASET_2"; + private static final String CONSTRAINTS_DATASET = "JDBC_CONSTRAINTS_TEST_DATASET"; + private static final String CONSTRAINTS_TABLE_NAME = "JDBC_CONSTRAINTS_TEST_TABLE"; + private static final String CONSTRAINTS_TABLE_NAME2 = "JDBC_CONSTRAINTS_TEST_TABLE2"; + private static final String CONSTRAINTS_TABLE_NAME3 = "JDBC_CONSTRAINTS_TEST_TABLE3"; + private static final String CALLABLE_STMT_PROC_NAME = "IT_CALLABLE_STMT_PROC_TEST"; + private static final String CALLABLE_STMT_TABLE_NAME = "IT_CALLABLE_STMT_PROC_TABLE"; + private static final String CALLABLE_STMT_PARAM_KEY = "CALL_STMT_PARAM_KEY"; + private static final String CALLABLE_STMT_DML_INSERT_PROC_NAME = + "IT_CALLABLE_STMT_PROC_DML_INSERT_TEST"; + private static final String CALLABLE_STMT_DML_UPDATE_PROC_NAME = + "IT_CALLABLE_STMT_PROC_DML_UPDATE_TEST"; + private static final String CALLABLE_STMT_DML_DELETE_PROC_NAME = + "IT_CALLABLE_STMT_PROC_DML_DELETE_TEST"; + private static final String CALLABLE_STMT_DML_TABLE_NAME = "IT_CALLABLE_STMT_PROC_DML_TABLE"; + private static final Long DEFAULT_CONN_POOL_SIZE = 10L; + private static final Long CUSTOM_CONN_POOL_SIZE = 5L; + private static final Object EXCEPTION_REPLACEMENT = "EXCEPTION-WAS-RAISED"; + + private static String requireEnvVar(String varName) { + String value = System.getenv(varName); + assertNotNull( + "Environment variable " + varName + " is required to perform these tests.", + System.getenv(varName)); + return value; + } + + private JsonObject getAuthJson() throws IOException { + final String secret = requireEnvVar("SA_SECRET"); + JsonObject authJson; + // Supporting both formats of SA_SECRET: + // - Local runs can point to a json file + // - Cloud Build has JSON value + try { + InputStream stream = Files.newInputStream(Paths.get(secret)); + InputStreamReader reader = new InputStreamReader(stream); + authJson = JsonParser.parseReader(reader).getAsJsonObject(); + } catch (IOException e) { + authJson = JsonParser.parseString(secret).getAsJsonObject(); + } + assertTrue(authJson.has("client_email")); + assertTrue(authJson.has("private_key")); + assertTrue(authJson.has("project_id")); + return authJson; + } + + @BeforeClass + public static void beforeClass() throws SQLException { + bigQueryConnection = DriverManager.getConnection(connection_uri, new Properties()); + bigQueryStatement = bigQueryConnection.createStatement(); + + Properties noReadApi = new Properties(); + noReadApi.setProperty("EnableHighThroughputAPI", "0"); + bigQueryConnectionNoReadApi = DriverManager.getConnection(connection_uri, noReadApi); + bigQueryStatementNoReadApi = bigQueryConnectionNoReadApi.createStatement(); + bigQuery = BigQueryOptions.newBuilder().build().getService(); + } + + @AfterClass + public static void afterClass() throws SQLException { + bigQueryStatement.close(); + bigQueryConnection.close(); + bigQueryStatementNoReadApi.close(); + bigQueryConnectionNoReadApi.close(); + } + + // TODO(kirl): Enable this test when pipeline has p12 secret available. + + // TODO(farhan): figure out how to programmatically generate an access token and test + + // TODO(obada): figure out how to programmatically generate a refresh token and test + + // This test is useing the same client email as a main authorization & impersonation. + // It requires account to have 'tokenCreator' permission, see + // https://cloud.google.com/docs/authentication/use-service-account-impersonation#required-roles + + // This test uses the same client email for the main authorization and a chain of impersonations. + // It requires the account to have 'tokenCreator' permission on itself. + + @Test + public void testSmallSelectAndVerifyResults() throws SQLException { + String query = + "SELECT repository_name FROM `bigquery-public-data.samples.github_timeline` WHERE" + + " repository_name LIKE 'X%' LIMIT 10"; + + ResultSet resultSet = bigQueryStatement.executeQuery(query); + int rowCount = 0; + while (resultSet.next()) { + assertTrue(resultSet.getString(1).startsWith("X")); + rowCount++; + } + assertEquals(10, rowCount); + } + + @Test + // reads without using ReadAPI and makes sure that they are in order, which implies threads worked + // correctly + public void testIterateOrderJsonMultiThread_NoReadApi() throws SQLException { + int expectedCnt = 10000; + String query = String.format(BASE_QUERY, expectedCnt); + ResultSet rs = bigQueryStatementNoReadApi.executeQuery(query); + int cnt = 0; + double oldTriDis = 0.0d; + while (rs.next()) { + double tripDis = rs.getDouble("trip_distance"); + ++cnt; + assertTrue(oldTriDis <= tripDis); + oldTriDis = tripDis; + } + assertEquals(expectedCnt, cnt); // all the records were retrieved + } + + @Test + public void testExecuteUpdate() throws SQLException { + String TABLE_NAME = "JDBC_EXECUTE_UPDATE_TABLE_" + randomNumber; + String createQuery = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER);", + DATASET, TABLE_NAME); + String insertQuery = + String.format( + "INSERT INTO %s.%s (StringField, IntegerField) " + + "VALUES ('string1',111 ), ('string2',111 ), ('string3',222 ), ('string4',333 );", + DATASET, TABLE_NAME); + String updateQuery = + String.format( + "UPDATE %s.%s SET StringField='Jane Doe' WHERE IntegerField=111", DATASET, TABLE_NAME); + String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); + String selectQuery = String.format("SELECT * FROM %s.%s", DATASET, TABLE_NAME); + + int createStatus = bigQueryStatement.executeUpdate(createQuery); + assertEquals(0, createStatus); + + int insertStatus = bigQueryStatement.executeUpdate(insertQuery); + assertEquals(4, insertStatus); + + bigQueryStatement.executeQuery(selectQuery); + int selectStatus = bigQueryStatement.getUpdateCount(); + assertEquals(-1, selectStatus); + + int updateStatus = bigQueryStatement.executeUpdate(updateQuery); + assertEquals(2, updateStatus); + + int dropStatus = bigQueryStatement.executeUpdate(dropQuery); + assertEquals(0, dropStatus); + + bigQueryStatement.execute(String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TABLE_NAME)); + } + + @Test + public void testExecuteMethod() throws SQLException { + + String TABLE_NAME = "JDBC_EXECUTE_TABLE_" + randomNumber; + String createQuery = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER);", + DATASET, TABLE_NAME); + String insertQuery = + String.format( + "INSERT INTO %s.%s (StringField, IntegerField) " + + "VALUES ('string1',111 ), ('string2',111 ), ('string3',222 ), ('string4',333 );", + DATASET, TABLE_NAME); + String updateQuery = + String.format( + "UPDATE %s.%s SET StringField='Jane Doe' WHERE IntegerField=111", DATASET, TABLE_NAME); + String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); + String selectQuery = String.format("SELECT * FROM %s.%s", DATASET, TABLE_NAME); + + boolean createStatus = bigQueryStatement.execute(createQuery); + assertFalse(createStatus); + + boolean insertStatus = bigQueryStatement.execute(insertQuery); + assertFalse(insertStatus); + + boolean selectStatus = bigQueryStatement.execute(selectQuery); + assertTrue(selectStatus); + int selectCount = bigQueryStatement.getUpdateCount(); + assertEquals(-1, selectCount); + ResultSet resultSet = bigQueryStatement.getResultSet(); + assertNotNull(resultSet); + + boolean updateStatus = bigQueryStatement.execute(updateQuery); + assertFalse(updateStatus); + + boolean dropStatus = bigQueryStatement.execute(dropQuery); + assertFalse(dropStatus); + } + + @Test + public void testPreparedExecuteMethod() throws SQLException { + + String TABLE_NAME = "JDBC_PREPARED_EXECUTE_TABLE_" + randomNumber; + String createQuery = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER);", + DATASET, TABLE_NAME); + String insertQuery = + String.format( + "INSERT INTO %s.%s (StringField, IntegerField) VALUES (?,?), (?,?), (?,?), (?,?);", + DATASET, TABLE_NAME); + String updateQuery = + String.format("UPDATE %s.%s SET StringField=? WHERE IntegerField=?", DATASET, TABLE_NAME); + String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); + String selectQuery = String.format("SELECT ? FROM %s.%s", DATASET, TABLE_NAME); + + boolean createStatus = bigQueryStatement.execute(createQuery); + assertFalse(createStatus); + + PreparedStatement insertStmt = bigQueryConnection.prepareStatement(insertQuery); + insertStmt.setString(1, "String1"); + insertStmt.setInt(2, 111); + insertStmt.setString(3, "String2"); + insertStmt.setInt(4, 222); + insertStmt.setString(5, "String3"); + insertStmt.setInt(6, 333); + insertStmt.setString(7, "String4"); + insertStmt.setInt(8, 444); + + boolean insertStatus = insertStmt.execute(); + assertFalse(insertStatus); + + PreparedStatement selectStmt = bigQueryConnection.prepareStatement(selectQuery); + selectStmt.setString(1, "StringField"); + boolean selectStatus = selectStmt.execute(); + assertTrue(selectStatus); + + int selectCount = selectStmt.getUpdateCount(); + assertEquals(-1, selectCount); + ResultSet resultSet = selectStmt.getResultSet(); + assertNotNull(resultSet); + + PreparedStatement updateStmt = bigQueryConnection.prepareStatement(updateQuery); + updateStmt.setString(1, "Jane Doe"); + updateStmt.setInt(2, 222); + boolean updateStatus = updateStmt.execute(); + assertFalse(updateStatus); + + boolean dropStatus = bigQueryStatement.execute(dropQuery); + assertFalse(dropStatus); + } + + @Test + public void testNonValidStatementTypeForAddBatchThrows() { + String BATCH_TABLE = "JDBC_EXECUTE_BATCH_TABLE_NON_VALID_TYPE_" + random.nextInt(99); + String createBatchTable = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);", + DATASET, BATCH_TABLE); + assertThrows( + IllegalArgumentException.class, () -> bigQueryStatement.addBatch(createBatchTable)); + } + + @Test + public void testUnsupportedHTAPIFallbacksToStandardQueriesWithRange() throws SQLException { + String selectQuery = "select * from `DATATYPERANGETEST.RangeIntervalTestTable` LIMIT 5000;"; + String connection_uri = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" + + "OAuthType=3;ProjectId=" + + PROJECT_ID + + ";MaxResults=500;HighThroughputActivationRatio=1;" + + "HighThroughputMinTableSize=100;" + + "EnableHighThroughputAPI=1;UnsupportedHTAPIFallback=1;JobCreationMode=1;"; + + // Read data via JDBC + Connection connection = DriverManager.getConnection(connection_uri); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(selectQuery); + assertNotNull(resultSet); + + ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); + resultSet.next(); + assertEquals(3, resultSetMetaData.getColumnCount()); + connection.close(); + } + + @Test + public void testValidLEPEndpointQuery() throws SQLException { + String DATASET = "JDBC_REGIONAL_DATASET"; + String TABLE_NAME = "REGIONAL_TABLE"; + String selectQuery = "select * from " + DATASET + "." + TABLE_NAME; + String connection_uri = + "jdbc:bigquery://https://googleapis.com/bigquery/v2:443;" + + "OAuthType=3;" + + "ProjectId=" + + PROJECT_ID + + ";" + + "EndpointOverrides=BIGQUERY=https://us-east4-bigquery.googleapis.com;"; + + // Read data via JDBC + Connection connection = DriverManager.getConnection(connection_uri); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(selectQuery); + assertNotNull(resultSet.getMetaData()); + connection.close(); + } + + @Test + public void testValidREPEndpointQuery() throws SQLException { + String DATASET = "JDBC_REGIONAL_DATASET"; + String TABLE_NAME = "REGIONAL_TABLE"; + String selectQuery = "select * from " + DATASET + "." + TABLE_NAME; + String connection_uri = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" + + "OAuthType=3;" + + "ProjectId=" + + PROJECT_ID + + ";" + + "EndpointOverrides=BIGQUERY=https://bigquery.us-east4.rep.googleapis.com;"; + + // Read data via JDBC + Connection connection = DriverManager.getConnection(connection_uri); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(selectQuery); + assertNotNull(resultSet.getMetaData()); + connection.close(); + } + + @Test + public void testCloseStatement() throws SQLException { + String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10"; + Statement statement = bigQueryConnection.createStatement(); + ResultSet jsonResultSet = statement.executeQuery(query); + assertEquals(10, resultSetRowCount(jsonResultSet)); + statement.close(); + assertTrue(statement.isClosed()); + } + + @Test + public void testCloseableStatementSingleResult() throws SQLException { + String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10"; + Statement statement = bigQueryConnection.createStatement(); + statement.closeOnCompletion(); + assertTrue(statement.isCloseOnCompletion()); + ResultSet jsonResultSet = statement.executeQuery(query); + assertFalse(statement.isClosed()); + jsonResultSet.close(); + assertTrue(statement.isClosed()); + } + + @Test + public void testCloseableStatementMultiResult() throws SQLException { + String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10;"; + Statement statement = bigQueryConnection.createStatement(); + statement.closeOnCompletion(); + assertTrue(statement.isCloseOnCompletion()); + statement.execute(query + query); + assertNotNull(statement.getResultSet()); + assertFalse(statement.isClosed()); + + assertTrue(statement.getMoreResults()); + assertNotNull(statement.getResultSet()); + assertFalse(statement.isClosed()); + + assertFalse(statement.getMoreResults()); + assertTrue(statement.isClosed()); + } + + @Test + public void testCloseableStatementMultiResultExplicitClose() throws SQLException { + String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 10;"; + Statement statement = bigQueryConnection.createStatement(); + statement.closeOnCompletion(); + assertTrue(statement.isCloseOnCompletion()); + statement.execute(query + query); + ResultSet result = statement.getResultSet(); + result.close(); + assertFalse(statement.isClosed()); + + assertTrue(statement.getMoreResults()); + result = statement.getResultSet(); + result.close(); + assertTrue(statement.isClosed()); + } + + @Test + public void testConnectionIsValid() throws SQLException { + assertTrue(bigQueryConnection.isValid(10)); + assertTrue(bigQueryConnectionNoReadApi.isValid(10)); + } + + @Test + public void testPreparedStatementExecuteUpdate() throws SQLException { + Random random = new Random(); + String DATASET = "JDBC_INTEGRATION_DATASET"; + String TABLE_NAME1 = "Inventory" + random.nextInt(9999); + String TABLE_NAME2 = "DetailedInventory" + random.nextInt(9999); + + String createQuery = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`product` STRING, `quantity` INTEGER);", + DATASET, TABLE_NAME1); + + String createQuery2 = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`product` STRING, `quantity` INTEGER," + + " `supply_constrained` BOOLEAN, `comment` STRING);", + DATASET, TABLE_NAME2); + + String insertQuery2 = + String.format( + "INSERT INTO %s.%s (product, quantity, supply_constrained, comment) " + + "VALUES ('countertop microwave', 20, NULL,'[]' )," + + " ('front load washer', 20, false,'[]' ), " + + " ('microwave', 20, false,'[]' ), " + + " ('refrigerator', 10, false,'[]' );", + DATASET, TABLE_NAME2); + + bigQueryStatement.execute(createQuery); + bigQueryStatement.execute(createQuery2); + bigQueryStatement.execute(insertQuery2); + + String insertQuery = + String.format( + "INSERT INTO %s.%s (product, quantity) " + "VALUES (?,? ), (?,? );", + DATASET, TABLE_NAME1); + PreparedStatement insertPs = bigQueryConnection.prepareStatement(insertQuery); + insertPs.setString(1, "dishwasher"); + insertPs.setInt(2, 30); + insertPs.setString(3, "dryer"); + insertPs.setInt(4, 30); + + int insertStatus = insertPs.executeUpdate(); + assertEquals(2, insertStatus); + + String updateQuery = + String.format("UPDATE %s.%s SET quantity=? WHERE product=?", DATASET, TABLE_NAME1); + PreparedStatement updatePs = bigQueryConnection.prepareStatement(updateQuery); + updatePs.setString(2, "dryer"); + updatePs.setInt(1, 35); + + int updateStatus = updatePs.executeUpdate(); + assertEquals(1, updateStatus); + + String deleteQuery = String.format("DELETE FROM %s.%s WHERE product=?", DATASET, TABLE_NAME1); + PreparedStatement deletePs = bigQueryConnection.prepareStatement(deleteQuery); + deletePs.setString(1, "dishwasher"); + + int deleteStatus = deletePs.executeUpdate(); + assertEquals(1, deleteStatus); + + String mergeQuery = + String.format( + "MERGE %s.%s T\n" + + "USING %s.%s S\n" + + "ON T.product = S.product\n" + + "WHEN NOT MATCHED AND quantity < ? THEN\n" + + " INSERT(product, quantity, supply_constrained, comment)\n" + + " VALUES(product, quantity, true, ?)\n" + + "WHEN NOT MATCHED THEN\n" + + " INSERT(product, quantity, supply_constrained)\n" + + " VALUES(product, quantity, false)", + DATASET, TABLE_NAME2, DATASET, TABLE_NAME1); + PreparedStatement mergePs = bigQueryConnection.prepareStatement(mergeQuery); + mergePs.setInt(1, 20); + mergePs.setString(2, "comment" + random.nextInt(999)); + + int mergeStatus = mergePs.executeUpdate(); + assertEquals(1, mergeStatus); + + ResultSet rs = + bigQueryStatement.executeQuery( + String.format("SELECT COUNT(*) AS row_count\n" + "FROM %s.%s", DATASET, TABLE_NAME2)); + rs.next(); + assertEquals(5, rs.getInt(1)); + + String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME1); + int dropStatus = bigQueryStatement.executeUpdate(dropQuery); + assertEquals(0, dropStatus); + bigQueryStatement.execute(String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME2)); + } + + @Test + public void testPreparedStatementDateTimeValues() throws SQLException { + Random random = new Random(); + String DATASET = "JDBC_INTEGRATION_DATASET"; + String TABLE_NAME1 = "DateTimeTestTable" + random.nextInt(9999); + + final String createTableQuery = + "CREATE OR REPLACE TABLE " + + " `%s.%s` " + + " (\n" + + "`StringField` STRING,\n" + + "`IntegerField` INTEGER,\n" + + "`TimestampField` TIMESTAMP,\n" + + "`TimeField` TIME,\n" + + "`DateField` DATE\n" + + ");"; + + String insertQuery = + String.format("INSERT INTO %s.%s VALUES (?,?,?,?,? );", DATASET, TABLE_NAME1); + + bigQueryStatement.execute(String.format(createTableQuery, DATASET, TABLE_NAME1)); + + PreparedStatement insertPs = bigQueryConnection.prepareStatement(insertQuery); + insertPs.setString(1, "dishwasher"); + insertPs.setInt(2, 1); + insertPs.setTimestamp(3, Timestamp.from(Instant.now())); + insertPs.setTime(4, Time.valueOf(LocalTime.NOON)); + insertPs.setDate(5, Date.valueOf("2025-12-3")); + + int insertStatus = insertPs.executeUpdate(); + assertEquals(1, insertStatus); + + ResultSet rs = + bigQueryStatement.executeQuery( + String.format("SELECT COUNT(*) AS row_count\n" + "FROM %s.%s", DATASET, TABLE_NAME1)); + rs.next(); + assertEquals(1, rs.getInt(1)); + + String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME1); + int dropStatus = bigQueryStatement.executeUpdate(dropQuery); + assertEquals(0, dropStatus); + } + + @Test + public void testTableConstraints() throws SQLException { + ResultSet primaryKey1 = + bigQueryConnection + .getMetaData() + .getPrimaryKeys(PROJECT_ID, CONSTRAINTS_DATASET, CONSTRAINTS_TABLE_NAME); + primaryKey1.next(); + assertEquals("id", primaryKey1.getString(4)); + assertFalse(primaryKey1.next()); + + ResultSet primaryKey2 = + bigQueryConnection + .getMetaData() + .getPrimaryKeys(PROJECT_ID, CONSTRAINTS_DATASET, CONSTRAINTS_TABLE_NAME2); + primaryKey2.next(); + assertEquals("first_name", primaryKey2.getString(4)); + primaryKey2.next(); + assertEquals("last_name", primaryKey2.getString(4)); + assertFalse(primaryKey2.next()); + + ResultSet foreignKeys = + bigQueryConnection + .getMetaData() + .getImportedKeys(PROJECT_ID, CONSTRAINTS_DATASET, CONSTRAINTS_TABLE_NAME); + foreignKeys.next(); + assertEquals(CONSTRAINTS_TABLE_NAME2, foreignKeys.getString(3)); + assertEquals("first_name", foreignKeys.getString(4)); + assertEquals("name", foreignKeys.getString(8)); + foreignKeys.next(); + assertEquals(CONSTRAINTS_TABLE_NAME2, foreignKeys.getString(3)); + assertEquals("last_name", foreignKeys.getString(4)); + assertEquals("second_name", foreignKeys.getString(8)); + foreignKeys.next(); + assertEquals(CONSTRAINTS_TABLE_NAME3, foreignKeys.getString(3)); + assertEquals("address", foreignKeys.getString(4)); + assertEquals("address", foreignKeys.getString(8)); + assertFalse(foreignKeys.next()); + + ResultSet crossReference = + bigQueryConnection + .getMetaData() + .getCrossReference( + PROJECT_ID, + CONSTRAINTS_DATASET, + CONSTRAINTS_TABLE_NAME2, + PROJECT_ID, + CONSTRAINTS_DATASET, + CONSTRAINTS_TABLE_NAME); + crossReference.next(); + assertEquals(CONSTRAINTS_TABLE_NAME2, crossReference.getString(3)); + assertEquals("first_name", crossReference.getString(4)); + assertEquals("name", crossReference.getString(8)); + crossReference.next(); + assertEquals("last_name", crossReference.getString(4)); + assertEquals("second_name", crossReference.getString(8)); + assertFalse(crossReference.next()); + } + + @Test + public void testDatabaseMetadataGetCatalogs() throws SQLException { + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + try (ResultSet rs = databaseMetaData.getCatalogs()) { + assertNotNull("ResultSet from getCatalogs() should not be null", rs); + + ResultSetMetaData rsmd = rs.getMetaData(); + assertNotNull("ResultSetMetaData should not be null", rsmd); + assertEquals("Should have one column", 1, rsmd.getColumnCount()); + assertEquals("Column name should be TABLE_CAT", "TABLE_CAT", rsmd.getColumnName(1)); + + assertTrue("ResultSet should have one row", rs.next()); + assertEquals("Catalog name should match Project ID", PROJECT_ID, rs.getString("TABLE_CAT")); + assertFalse("ResultSet should have no more rows", rs.next()); + } + } + + @Test + public void testDatabaseMetadataGetProcedures() throws SQLException { + String DATASET = "JDBC_INTEGRATION_DATASET"; + String procedureName = "create_customer"; + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + ResultSet resultSet = databaseMetaData.getProcedures(PROJECT_ID, DATASET, procedureName); + while (resultSet.next()) { + assertEquals(PROJECT_ID, resultSet.getString("PROCEDURE_CAT")); + assertEquals(DATASET, resultSet.getString("PROCEDURE_SCHEM")); + assertEquals(procedureName, resultSet.getString("PROCEDURE_NAME")); + assertEquals(procedureName, resultSet.getString("SPECIFIC_NAME")); + assertEquals(DatabaseMetaData.procedureResultUnknown, resultSet.getInt("PROCEDURE_TYPE")); + } + } + + @Test + public void testDatabaseMetadataGetProcedureColumns() throws SQLException { + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + + // --- Test Case 1: Specific schema and procedure, null column name pattern --- + String specificSchema = "JDBC_INTEGRATION_DATASET"; + String specificProcedure = "create_customer"; + ResultSet resultSet = + databaseMetaData.getProcedureColumns(PROJECT_ID, specificSchema, specificProcedure, null); + int specificProcRows = 0; + boolean foundNameParam = false; + boolean foundIdParam = false; + while (resultSet.next()) { + specificProcRows++; + assertEquals(PROJECT_ID, resultSet.getString("PROCEDURE_CAT")); + assertEquals(specificSchema, resultSet.getString("PROCEDURE_SCHEM")); + assertEquals(specificProcedure, resultSet.getString("PROCEDURE_NAME")); + assertEquals(specificProcedure, resultSet.getString("SPECIFIC_NAME")); + if ("name".equals(resultSet.getString("COLUMN_NAME"))) { + foundNameParam = true; + assertEquals(1, resultSet.getInt("ORDINAL_POSITION")); + } + if ("id".equals(resultSet.getString("COLUMN_NAME"))) { + foundIdParam = true; + assertEquals(2, resultSet.getInt("ORDINAL_POSITION")); + } + } + assertEquals("Should find 2 parameters for " + specificProcedure, 2, specificProcRows); + assertTrue("Parameter 'name' should be found", foundNameParam); + assertTrue("Parameter 'id' should be found", foundIdParam); + resultSet.close(); + + // --- Test Case 2: Specific schema, procedure, and column name pattern --- + String specificColumn = "name"; + resultSet = + databaseMetaData.getProcedureColumns( + PROJECT_ID, specificSchema, specificProcedure, specificColumn); + assertTrue("Should find the specific column 'name'", resultSet.next()); + assertEquals(PROJECT_ID, resultSet.getString("PROCEDURE_CAT")); + assertEquals(specificSchema, resultSet.getString("PROCEDURE_SCHEM")); + assertEquals(specificProcedure, resultSet.getString("PROCEDURE_NAME")); + assertEquals(specificColumn, resultSet.getString("COLUMN_NAME")); + assertEquals(1, resultSet.getInt("ORDINAL_POSITION")); + assertEquals( + (short) DatabaseMetaData.procedureColumnUnknown, resultSet.getShort("COLUMN_TYPE")); + assertEquals(java.sql.Types.NVARCHAR, resultSet.getInt("DATA_TYPE")); + assertEquals("NVARCHAR", resultSet.getString("TYPE_NAME")); + assertFalse("Should only find one row for exact column match", resultSet.next()); + resultSet.close(); + + // --- Test Case 3: Non-existent procedure --- + resultSet = + databaseMetaData.getProcedureColumns( + PROJECT_ID, specificSchema, "non_existent_procedure_xyz", null); + assertFalse("Should not find columns for a non-existent procedure", resultSet.next()); + resultSet.close(); + } + + @Test + public void testDatabaseMetadataGetColumns() throws SQLException { + String DATASET = "JDBC_INTEGRATION_DATASET"; + String TABLE_NAME = "JDBC_DATATYPES_INTEGRATION_TEST_TABLE"; + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + + // --- Test Case 1: Specific Column (StringField) --- + ResultSet resultSet = + databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "StringField"); + + assertTrue(resultSet.next()); + assertEquals(PROJECT_ID, resultSet.getString("TABLE_CAT")); + assertEquals(DATASET, resultSet.getString("TABLE_SCHEM")); + assertEquals(TABLE_NAME, resultSet.getString("TABLE_NAME")); + assertEquals("StringField", resultSet.getString("COLUMN_NAME")); + assertEquals("NVARCHAR", resultSet.getString("TYPE_NAME")); + resultSet.getObject("COLUMN_SIZE"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(6, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 2: All Columns --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, null); + assertTrue(resultSet.next()); + int count = 0; + do { + count++; + assertEquals(PROJECT_ID, resultSet.getString("TABLE_CAT")); + assertEquals(DATASET, resultSet.getString("TABLE_SCHEM")); + assertEquals(TABLE_NAME, resultSet.getString("TABLE_NAME")); + assertNotNull(resultSet.getString("COLUMN_NAME")); + } while (resultSet.next()); + assertEquals(16, count); + + // --- Test Case 3: Column Name Pattern Matching (%Field) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "%Time%"); + assertTrue(resultSet.next()); + count = 0; + do { + count++; + String columnName = resultSet.getString("COLUMN_NAME"); + assertTrue(columnName.contains("Time")); + } while (resultSet.next()); + assertEquals(3, count); + + // --- Test Case 4: Column Name Pattern Matching (Integer%) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "Integer%"); + assertTrue(resultSet.next()); + assertEquals("IntegerField", resultSet.getString("COLUMN_NAME")); + assertEquals("BIGINT", resultSet.getString("TYPE_NAME")); + assertEquals(19, resultSet.getInt("COLUMN_SIZE")); + assertEquals(0, resultSet.getInt("DECIMAL_DIGITS")); + assertEquals(10, resultSet.getInt("NUM_PREC_RADIX")); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(2, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 5: Specific Column (BooleanField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "BooleanField"); + assertTrue(resultSet.next()); + assertEquals("BooleanField", resultSet.getString("COLUMN_NAME")); + assertEquals("BOOLEAN", resultSet.getString("TYPE_NAME")); + assertEquals(1, resultSet.getInt("COLUMN_SIZE")); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(1, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 6: Specific Column (NumericField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "NumericField"); + assertTrue(resultSet.next()); + assertEquals("NumericField", resultSet.getString("COLUMN_NAME")); + assertEquals("NUMERIC", resultSet.getString("TYPE_NAME")); + assertEquals(38, resultSet.getInt("COLUMN_SIZE")); + assertEquals(9, resultSet.getInt("DECIMAL_DIGITS")); + assertEquals(10, resultSet.getInt("NUM_PREC_RADIX")); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(4, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 7: Specific Column (BytesField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "BytesField"); + assertTrue(resultSet.next()); + assertEquals("BytesField", resultSet.getString("COLUMN_NAME")); + assertEquals("VARBINARY", resultSet.getString("TYPE_NAME")); + resultSet.getObject("COLUMN_SIZE"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(7, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 8: Specific Column (ArrayField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "ArrayField"); + assertTrue(resultSet.next()); + assertEquals("ArrayField", resultSet.getString("COLUMN_NAME")); + assertEquals("ARRAY", resultSet.getString("TYPE_NAME")); + resultSet.getObject("COLUMN_SIZE"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(9, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 9: Specific Column (TimestampField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "TimestampField"); + assertTrue(resultSet.next()); + assertEquals("TimestampField", resultSet.getString("COLUMN_NAME")); + assertEquals("TIMESTAMP", resultSet.getString("TYPE_NAME")); + assertEquals(29, resultSet.getInt("COLUMN_SIZE")); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(10, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 10: Specific Column (DateField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "DateField"); + assertTrue(resultSet.next()); + assertEquals("DateField", resultSet.getString("COLUMN_NAME")); + assertEquals("DATE", resultSet.getString("TYPE_NAME")); + assertEquals(10, resultSet.getInt("COLUMN_SIZE")); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(11, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 11: Specific Column (TimeField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "TimeField"); + assertTrue(resultSet.next()); + assertEquals("TimeField", resultSet.getString("COLUMN_NAME")); + assertEquals("TIME", resultSet.getString("TYPE_NAME")); + assertEquals(15, resultSet.getInt("COLUMN_SIZE")); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(12, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 12: Specific Column (DateTimeField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "DateTimeField"); + assertTrue(resultSet.next()); + assertEquals("DateTimeField", resultSet.getString("COLUMN_NAME")); + assertEquals("TIMESTAMP", resultSet.getString("TYPE_NAME")); + assertEquals(29, resultSet.getInt("COLUMN_SIZE")); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(13, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + + // --- Test Case 13: Specific Column (GeographyField) --- + resultSet = databaseMetaData.getColumns(PROJECT_ID, DATASET, TABLE_NAME, "GeographyField"); + assertTrue(resultSet.next()); + assertEquals("GeographyField", resultSet.getString("COLUMN_NAME")); + assertEquals("VARCHAR", resultSet.getString("TYPE_NAME")); + resultSet.getObject("COLUMN_SIZE"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("DECIMAL_DIGITS"); + assertTrue(resultSet.wasNull()); + resultSet.getObject("NUM_PREC_RADIX"); + assertTrue(resultSet.wasNull()); + assertEquals(1, resultSet.getInt("NULLABLE")); + assertEquals(14, resultSet.getInt("ORDINAL_POSITION")); + assertFalse(resultSet.next()); + } + + @Test + public void testDatabaseMetadataGetTables() throws SQLException { + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + String DATASET = "JDBC_TABLE_TYPES_TEST"; + + // --- Test Case 1: Get all tables (types = null) --- + ResultSet rsAll = databaseMetaData.getTables(PROJECT_ID, DATASET, null, null); + Set allTableNames = new HashSet<>(); + while (rsAll.next()) { + allTableNames.add(rsAll.getString("TABLE_NAME")); + } + assertTrue(allTableNames.contains("base_table")); + assertTrue(allTableNames.contains("my_view")); + assertTrue(allTableNames.contains("external_table")); + assertTrue(allTableNames.contains("my_materialized_view")); + assertTrue(allTableNames.contains("base_table_clone")); + assertTrue(allTableNames.contains("base_table_snapshot")); + assertEquals(6, allTableNames.size()); + + // --- Test Case 2: Get only "TABLE" type --- + ResultSet rsTable = + databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"TABLE"}); + Set tableNames = new HashSet<>(); + while (rsTable.next()) { + tableNames.add(rsTable.getString("TABLE_NAME")); + } + assertTrue(tableNames.contains("base_table")); + assertTrue(tableNames.contains("base_table_clone")); + assertEquals(2, tableNames.size()); + + // --- Test Case 3: Get "VIEW" type --- + ResultSet rsView = databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"VIEW"}); + assertTrue(rsView.next()); + assertEquals("my_view", rsView.getString("TABLE_NAME")); + assertEquals("VIEW", rsView.getString("TABLE_TYPE")); + assertFalse(rsView.next()); + + // --- Test Case 4: Get "EXTERNAL TABLE" type --- + ResultSet rsExternal = + databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"EXTERNAL"}); + assertTrue(rsExternal.next()); + assertEquals("external_table", rsExternal.getString("TABLE_NAME")); + assertEquals("EXTERNAL", rsExternal.getString("TABLE_TYPE")); + assertFalse(rsExternal.next()); + + // --- Test Case 5: Get "MATERIALIZED_VIEW" type --- + ResultSet rsMaterialized = + databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"MATERIALIZED_VIEW"}); + assertTrue(rsMaterialized.next()); + assertEquals("my_materialized_view", rsMaterialized.getString("TABLE_NAME")); + assertEquals("MATERIALIZED_VIEW", rsMaterialized.getString("TABLE_TYPE")); + assertFalse(rsMaterialized.next()); + + // --- Test Case 6: Get "SNAPSHOT" type --- + ResultSet rsSnapshot = + databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"SNAPSHOT"}); + assertTrue(rsSnapshot.next()); + assertEquals("base_table_snapshot", rsSnapshot.getString("TABLE_NAME")); + assertEquals("SNAPSHOT", rsSnapshot.getString("TABLE_TYPE")); + assertFalse(rsSnapshot.next()); + + // --- Test Case 8: Get multiple types ("TABLE" and "VIEW") --- + ResultSet rsMulti = + databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {"TABLE", "VIEW"}); + Set multiTableNames = new HashSet<>(); + while (rsMulti.next()) { + multiTableNames.add(rsMulti.getString("TABLE_NAME")); + } + assertTrue(multiTableNames.contains("base_table")); + assertTrue(multiTableNames.contains("base_table_clone")); + assertTrue(multiTableNames.contains("my_view")); + assertEquals(3, multiTableNames.size()); + + // --- Test Case 9: tableNamePattern --- + ResultSet rsNamePattern = databaseMetaData.getTables(PROJECT_ID, DATASET, "base%", null); + Set baseTableNames = new HashSet<>(); + while (rsNamePattern.next()) { + baseTableNames.add(rsNamePattern.getString("TABLE_NAME")); + } + assertTrue(baseTableNames.contains("base_table")); + assertTrue(baseTableNames.contains("base_table_clone")); + assertTrue(baseTableNames.contains("base_table_snapshot")); + assertEquals(3, baseTableNames.size()); + + // --- Test Case 10: No matching table --- + ResultSet rsNoMatch = + databaseMetaData.getTables(PROJECT_ID, DATASET, "nonexistent_table", null); + assertFalse(rsNoMatch.next()); + + // --- Test Case 11: Null type in array --- + ResultSet rsNullType = + databaseMetaData.getTables(PROJECT_ID, DATASET, null, new String[] {null, "VIEW"}); + assertTrue(rsNullType.next()); + assertEquals("VIEW", rsNullType.getString("TABLE_TYPE")); + assertEquals("my_view", rsNullType.getString("TABLE_NAME")); + assertFalse(rsNullType.next()); + } + + @Test + public void testDatabaseMetadataGetSchemas() throws SQLException { + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + + // Test case 1: Get all schemas with catalog and check for the presence of specific schemas + ResultSet rsAll = databaseMetaData.getSchemas(PROJECT_ID, null); + Set actualSchemas = new HashSet<>(); + while (rsAll.next()) { + assertEquals(PROJECT_ID, rsAll.getString("TABLE_CATALOG")); + actualSchemas.add(rsAll.getString("TABLE_SCHEM")); + } + assertTrue(actualSchemas.contains("JDBC_INTEGRATION_DATASET")); + assertTrue(actualSchemas.contains("JDBC_TABLE_TYPES_TEST")); + assertTrue(actualSchemas.contains("ODBC_TEST_DATASET")); + + // Test case 2: Get schemas with catalog and schemaPattern matching "JDBC_NIGHTLY_IT_DATASET" + ResultSet rsPattern = databaseMetaData.getSchemas(PROJECT_ID, "JDBC_NIGHTLY_IT_DATASET"); + Set actualSchemasPattern = new HashSet<>(); + while (rsPattern.next()) { + assertEquals(PROJECT_ID, rsPattern.getString("TABLE_CATALOG")); + actualSchemasPattern.add(rsPattern.getString("TABLE_SCHEM")); + } + assertTrue(actualSchemasPattern.contains("JDBC_NIGHTLY_IT_DATASET")); + assertEquals(1, actualSchemasPattern.size()); + + // Test case 3: Get schemas with catalog and schemaPattern matching "nonexistent" + ResultSet rsNoMatch = databaseMetaData.getSchemas(PROJECT_ID, "nonexistent"); + assertFalse(rsNoMatch.next()); + + // Test case 4: Get schemas with non-existent catalog + rsNoMatch = databaseMetaData.getSchemas("invalid-catalog", null); + assertFalse(rsNoMatch.next()); + } + + @Test + public void testDatabaseMetadataGetSchemasNoArgs() throws SQLException { + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + String expectedCatalog = bigQueryConnection.getCatalog(); + assertNotNull("Project ID (catalog) from connection should not be null", expectedCatalog); + + // Test case: Get all schemas (datasets) for the current project + try (ResultSet rsAll = databaseMetaData.getSchemas()) { + assertNotNull("ResultSet from getSchemas() should not be null", rsAll); + boolean foundTestDataset = false; + int rowCount = 0; + while (rsAll.next()) { + rowCount++; + assertEquals( + "TABLE_CATALOG should match the connection's project ID", + expectedCatalog, + rsAll.getString("TABLE_CATALOG")); + String schemaName = rsAll.getString("TABLE_SCHEM"); + assertNotNull("TABLE_SCHEM should not be null", schemaName); + if (DATASET.equals(schemaName) + || DATASET2.equals(schemaName) + || CONSTRAINTS_DATASET.equals(schemaName) + || "JDBC_TABLE_TYPES_TEST".equals(schemaName) + || "JDBC_INTEGRATION_DATASET".equals(schemaName)) { + foundTestDataset = true; + } + } + assertTrue("At least one of the known test datasets should be found", foundTestDataset); + assertTrue("Should retrieve at least one schema/dataset", rowCount > 0); + } + } + + @Test + public void testDatabaseMetaDataGetFunctions() throws SQLException { + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + String testSchema = "JDBC_TABLE_TYPES_TEST"; + String testCatalog = PROJECT_ID; + + Set expectedFunctionNames = + new HashSet<>( + Arrays.asList( + "complex_scalar_sql_udf", + "persistent_sql_udf_named_params", + "scalar_js_udf", + "scalar_sql_udf")); + + // Test 1: Get all functions from a specific schema + ResultSet rsAll = databaseMetaData.getFunctions(testCatalog, testSchema, null); + Set foundFunctionNames = new HashSet<>(); + int countAll = 0; + while (rsAll.next()) { + countAll++; + assertEquals(testCatalog, rsAll.getString("FUNCTION_CAT")); + assertEquals(testSchema, rsAll.getString("FUNCTION_SCHEM")); + String funcName = rsAll.getString("FUNCTION_NAME"); + foundFunctionNames.add(funcName); + assertNull(rsAll.getString("REMARKS")); + assertEquals(DatabaseMetaData.functionResultUnknown, rsAll.getShort("FUNCTION_TYPE")); + assertEquals(funcName, rsAll.getString("SPECIFIC_NAME")); + } + assertEquals( + "Should find all " + expectedFunctionNames.size() + " functions in " + testSchema, + expectedFunctionNames.size(), + countAll); + assertEquals(expectedFunctionNames, foundFunctionNames); + rsAll.close(); + + // Test 2: Get a specific function using functionNamePattern + String specificFunctionName = "scalar_sql_udf"; + ResultSet rsSpecific = + databaseMetaData.getFunctions(testCatalog, testSchema, specificFunctionName); + assertTrue("Should find the specific function " + specificFunctionName, rsSpecific.next()); + assertEquals(testCatalog, rsSpecific.getString("FUNCTION_CAT")); + assertEquals(testSchema, rsSpecific.getString("FUNCTION_SCHEM")); + assertEquals(specificFunctionName, rsSpecific.getString("FUNCTION_NAME")); + assertNull(rsSpecific.getString("REMARKS")); + assertEquals(DatabaseMetaData.functionResultUnknown, rsSpecific.getShort("FUNCTION_TYPE")); + assertEquals(specificFunctionName, rsSpecific.getString("SPECIFIC_NAME")); + assertFalse("Should only find one row for exact function match", rsSpecific.next()); + rsSpecific.close(); + + // Test 3: Get functions using a wildcard functionNamePattern "scalar%" + // Expected order due to sorting: scalar_js_udf, scalar_sql_udf + ResultSet rsWildcard = databaseMetaData.getFunctions(testCatalog, testSchema, "scalar%"); + assertTrue("Should find functions matching 'scalar%'", rsWildcard.next()); + assertEquals("scalar_js_udf", rsWildcard.getString("FUNCTION_NAME")); + assertEquals(DatabaseMetaData.functionResultUnknown, rsWildcard.getShort("FUNCTION_TYPE")); + + assertTrue("Should find the second function matching 'scalar%'", rsWildcard.next()); + assertEquals("scalar_sql_udf", rsWildcard.getString("FUNCTION_NAME")); + assertEquals(DatabaseMetaData.functionResultUnknown, rsWildcard.getShort("FUNCTION_TYPE")); + assertFalse("Should be no more functions matching 'scalar%'", rsWildcard.next()); + rsWildcard.close(); + + // Test 4: Schema pattern with wildcard + ResultSet rsSchemaWildcard = + databaseMetaData.getFunctions(testCatalog, "JDBC_TABLE_TYPES_T%", "complex_scalar_sql_udf"); + assertTrue("Should find function with schema wildcard", rsSchemaWildcard.next()); + assertEquals(testSchema, rsSchemaWildcard.getString("FUNCTION_SCHEM")); + assertEquals("complex_scalar_sql_udf", rsSchemaWildcard.getString("FUNCTION_NAME")); + assertFalse( + "Should only find one row for this schema wildcard and specific function", + rsSchemaWildcard.next()); + rsSchemaWildcard.close(); + + // Test 5: Non-existent function + ResultSet rsNonExistentFunc = + databaseMetaData.getFunctions(testCatalog, testSchema, "non_existent_function_xyz123"); + assertFalse("Should not find a non-existent function", rsNonExistentFunc.next()); + rsNonExistentFunc.close(); + + // Test 6: Non-existent schema + ResultSet rsNonExistentSchema = + databaseMetaData.getFunctions(testCatalog, "NON_EXISTENT_SCHEMA_XYZ123", null); + assertFalse("Should not find functions in a non-existent schema", rsNonExistentSchema.next()); + rsNonExistentSchema.close(); + + // Test 7: Empty schema pattern + ResultSet rsEmptySchema = databaseMetaData.getFunctions(testCatalog, "", null); + assertFalse("Empty schema pattern should return no results", rsEmptySchema.next()); + rsEmptySchema.close(); + + // Test 8: Empty function name pattern + ResultSet rsEmptyFunction = databaseMetaData.getFunctions(testCatalog, testSchema, ""); + assertFalse("Empty function name pattern should return no results", rsEmptyFunction.next()); + rsEmptyFunction.close(); + + // Test 9: Null catalog + ResultSet rsNullCatalog = databaseMetaData.getFunctions(null, testSchema, null); + assertFalse("Null catalog should return no results", rsNullCatalog.next()); + rsNullCatalog.close(); + } + + @Test + public void testDatabaseMetadataGetFunctionColumns() throws SQLException { + DatabaseMetaData databaseMetaData = bigQueryConnection.getMetaData(); + String testCatalog = PROJECT_ID; + String testSchema = "JDBC_TABLE_TYPES_TEST"; + + // Test Case 1: Specific function 'scalar_sql_udf', specific column 'x' + String specificFunction1 = "scalar_sql_udf"; + String specificColumn1 = "x"; + ResultSet rs = + databaseMetaData.getFunctionColumns( + testCatalog, testSchema, specificFunction1, specificColumn1); + + assertTrue("Should find column 'x' for function 'scalar_sql_udf'", rs.next()); + assertEquals(testCatalog, rs.getString("FUNCTION_CAT")); + assertEquals(testSchema, rs.getString("FUNCTION_SCHEM")); + assertEquals(specificFunction1, rs.getString("FUNCTION_NAME")); + assertEquals(specificColumn1, rs.getString("COLUMN_NAME")); + assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); + assertEquals(Types.BIGINT, rs.getInt("DATA_TYPE")); + assertEquals("BIGINT", rs.getString("TYPE_NAME")); + assertEquals(19, rs.getInt("PRECISION")); + assertEquals(null, rs.getObject("LENGTH")); + assertTrue(rs.wasNull()); + assertEquals(0, rs.getShort("SCALE")); + assertEquals(10, rs.getShort("RADIX")); + assertEquals(DatabaseMetaData.functionNullableUnknown, rs.getShort("NULLABLE")); + assertNull(rs.getString("REMARKS")); + assertEquals(null, rs.getObject("CHAR_OCTET_LENGTH")); + assertTrue(rs.wasNull()); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("", rs.getString("IS_NULLABLE")); + assertEquals(specificFunction1, rs.getString("SPECIFIC_NAME")); + assertFalse("Should only find one row for exact column match", rs.next()); + rs.close(); + + // Test Case 2: Specific function 'complex_scalar_sql_udf', specific column 'arr' + String specificFunction2 = "complex_scalar_sql_udf"; + String specificColumn2 = "arr"; + rs = + databaseMetaData.getFunctionColumns( + testCatalog, testSchema, specificFunction2, specificColumn2); + assertTrue("Should find column 'arr' for function 'complex_scalar_sql_udf'", rs.next()); + assertEquals(testCatalog, rs.getString("FUNCTION_CAT")); + assertEquals(testSchema, rs.getString("FUNCTION_SCHEM")); + assertEquals(specificFunction2, rs.getString("FUNCTION_NAME")); + assertEquals(specificColumn2, rs.getString("COLUMN_NAME")); + assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); + assertEquals(Types.ARRAY, rs.getInt("DATA_TYPE")); + assertEquals("ARRAY", rs.getString("TYPE_NAME")); + assertEquals(null, rs.getObject("PRECISION")); + assertTrue(rs.wasNull()); + assertEquals(null, rs.getObject("LENGTH")); + assertTrue(rs.wasNull()); + assertEquals(null, rs.getObject("SCALE")); + assertTrue(rs.wasNull()); + assertEquals(null, rs.getObject("RADIX")); + assertTrue(rs.wasNull()); + assertEquals(DatabaseMetaData.functionNullableUnknown, rs.getShort("NULLABLE")); + assertNull(rs.getString("REMARKS")); + assertEquals(null, rs.getObject("CHAR_OCTET_LENGTH")); + assertTrue(rs.wasNull()); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("", rs.getString("IS_NULLABLE")); + assertEquals(specificFunction2, rs.getString("SPECIFIC_NAME")); + assertFalse("Should only find one row for exact column match", rs.next()); + rs.close(); + + // Test Case 3: All columns for 'persistent_sql_udf_named_params' (sorted by ordinal position) + String specificFunction3 = "persistent_sql_udf_named_params"; + rs = databaseMetaData.getFunctionColumns(testCatalog, testSchema, specificFunction3, null); + assertTrue("Should find columns for " + specificFunction3, rs.next()); + assertEquals(specificFunction3, rs.getString("FUNCTION_NAME")); + assertEquals("value1", rs.getString("COLUMN_NAME")); // Ordinal Position 1 + assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); + assertEquals(Types.BIGINT, rs.getInt("DATA_TYPE")); + assertEquals("BIGINT", rs.getString("TYPE_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + + assertTrue("Should find second column for " + specificFunction3, rs.next()); + assertEquals(specificFunction3, rs.getString("FUNCTION_NAME")); + assertEquals("value-two", rs.getString("COLUMN_NAME")); // Ordinal Position 2 + assertEquals(DatabaseMetaData.functionColumnUnknown, rs.getShort("COLUMN_TYPE")); + assertEquals(Types.NVARCHAR, rs.getInt("DATA_TYPE")); + assertEquals("NVARCHAR", rs.getString("TYPE_NAME")); + assertEquals(2, rs.getInt("ORDINAL_POSITION")); + assertFalse("Should be no more columns for " + specificFunction3, rs.next()); + rs.close(); + + // Test Case 4: Wildcard for function name "scalar%", specific column name "x" + rs = databaseMetaData.getFunctionColumns(testCatalog, testSchema, "scalar%", "x"); + assertTrue("Should find column 'x' for functions matching 'scalar%'", rs.next()); + assertEquals("scalar_sql_udf", rs.getString("FUNCTION_NAME")); + assertEquals("x", rs.getString("COLUMN_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertFalse("Should be no more columns named 'x' for functions matching 'scalar%'", rs.next()); + rs.close(); + + // Test Case 5: Wildcard for column name "%" for 'scalar_js_udf' + String specificFunction4 = "scalar_js_udf"; + rs = databaseMetaData.getFunctionColumns(testCatalog, testSchema, specificFunction4, "%"); + assertTrue("Should find columns for " + specificFunction4 + " with wildcard", rs.next()); + assertEquals(specificFunction4, rs.getString("FUNCTION_NAME")); + assertEquals("name", rs.getString("COLUMN_NAME")); // Ordinal Position 1 + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + + assertTrue("Should find second column for " + specificFunction4 + " with wildcard", rs.next()); + assertEquals(specificFunction4, rs.getString("FUNCTION_NAME")); + assertEquals("age", rs.getString("COLUMN_NAME")); // Ordinal Position 2 + assertEquals(2, rs.getInt("ORDINAL_POSITION")); + assertFalse("Should be no more columns for " + specificFunction4 + " with wildcard", rs.next()); + rs.close(); + + // Test Case 6: Non-existent function + rs = + databaseMetaData.getFunctionColumns( + testCatalog, testSchema, "non_existent_function_xyz", null); + assertFalse("Should not find columns for a non-existent function", rs.next()); + rs.close(); + } + + @Test + public void testRangeDataTypeWithJsonResultSet() throws SQLException { + String RANGE_DATA_TABLE = "JDBC_RANGE_DATA_TEST_TABLE_" + random.nextInt(99); + String range_date_literal = "RANGE '[2020-01-01, 2020-01-31)'"; + String range_datetime_literal = "RANGE '[2020-01-01 12:00:00, 2020-01-31 12:00:00)'"; + String range_timestamp_literal = + "RANGE '[2020-01-01 12:00:00+08, 2020-01-31 12:00:00+08)'"; + + String createRangeTable = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `range_date` RANGE," + + " `range_date_time` RANGE, `range_timestamp` RANGE);", + DATASET, RANGE_DATA_TABLE); + String insertQuery = + String.format( + "INSERT INTO %s.%s (id, range_date, range_date_time, range_timestamp) VALUES (1, %s," + + " %s, %s);", + DATASET, + RANGE_DATA_TABLE, + range_date_literal, + range_datetime_literal, + range_timestamp_literal); + String selectQuery = + String.format( + "SELECT id, range_date, range_date_time, range_timestamp FROM %s.%s WHERE id = 1;", + DATASET, RANGE_DATA_TABLE); + + boolean status = bigQueryStatement.execute(createRangeTable); + assertFalse(status); + + status = bigQueryStatement.execute(insertQuery); + assertFalse(status); + + ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); + + Integer numRows = 0; + String actual_range_date = ""; + String actual_range_datetime = ""; + String actual_range_timestamp = ""; + + while (resultSet.next()) { + numRows++; + actual_range_date = resultSet.getString("range_date"); + actual_range_datetime = resultSet.getString("range_date_time"); + actual_range_timestamp = resultSet.getString("range_timestamp"); + } + + String expected_range_date = "[2020-01-01, 2020-01-31)"; + String expected_range_datetime = "[2020-01-01T12:00:00, 2020-01-31T12:00:00)"; + String expected_range_timestamp = "[1577851200.000000, 1580443200.000000)"; + + assertThat(numRows).isEqualTo(1); + assertThat(actual_range_date).isEqualTo(expected_range_date); + assertThat(actual_range_datetime).isEqualTo(expected_range_datetime); + assertThat(actual_range_timestamp).isEqualTo(expected_range_timestamp); + + bigQueryStatement.execute( + String.format("DROP TABLE IF EXISTS %S.%s", DATASET, RANGE_DATA_TABLE)); + } + + @Test + public void testPrepareCallSql() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc"); + assertNotNull(callableStatement); + callableStatement.close(); + } + + @Test + public void testRegisterOutParamIndex() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + callableStatement.registerOutParameter(1, Types.VARCHAR); + callableStatement.close(); + } + + @Test + public void testRegisterOutParamName() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + callableStatement.registerOutParameter("ParamKey", Types.VARCHAR); + callableStatement.close(); + } + + @Test + public void testRegisterOutParamIndexScale() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + callableStatement.registerOutParameter(1, Types.NUMERIC, 2); + callableStatement.close(); + } + + @Test + public void testRegisterOutParamNameScale() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + callableStatement.registerOutParameter("ParamKey", Types.NUMERIC, 2); + callableStatement.close(); + } + + @Test + public void testPrepareCallSqlResultSetTypeConcurrency() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + assertNotNull(callableStatement); + callableStatement.close(); + } + + @Test + public void testPrepareCallConcurrencyRegisterOutParamIndex() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + assertNotNull(callableStatement); + callableStatement.registerOutParameter(1, Types.VARCHAR); + callableStatement.close(); + } + + @Test + public void testPrepareCallConcurrencyRegisterOutParamName() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + assertNotNull(callableStatement); + callableStatement.registerOutParameter("ParamKey", Types.VARCHAR); + callableStatement.close(); + } + + @Test + public void testPrepareCallConcurrencyRegisterOutParamIndexScale() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + assertNotNull(callableStatement); + callableStatement.registerOutParameter(1, Types.NUMERIC, 2); + callableStatement.close(); + } + + @Test + public void testPrepareCallConcurrencyRegisterOutParamNameScale() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + assertNotNull(callableStatement); + callableStatement.registerOutParameter("ParamKey", Types.NUMERIC, 2); + callableStatement.close(); + } + + @Test + public void testPrepareCallSqlResultSetTypeConcurrencyHoldability() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.CLOSE_CURSORS_AT_COMMIT); + assertNotNull(callableStatement); + callableStatement.close(); + } + + @Test + public void testPrepareCallHoldabilityRegisterOutParamIndex() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.CLOSE_CURSORS_AT_COMMIT); + assertNotNull(callableStatement); + callableStatement.registerOutParameter(1, Types.VARCHAR); + callableStatement.close(); + } + + @Test + public void testPrepareCallHoldabilityRegisterOutParamName() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.CLOSE_CURSORS_AT_COMMIT); + assertNotNull(callableStatement); + callableStatement.registerOutParameter("ParamKey", Types.VARCHAR); + callableStatement.close(); + } + + @Test + public void testPrepareCallHoldabilityRegisterOutParamIndexScale() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.CLOSE_CURSORS_AT_COMMIT); + assertNotNull(callableStatement); + callableStatement.close(); + } + + @Test + public void testPrepareCallHoldabilityRegisterOutParamNameScale() throws SQLException { + CallableStatement callableStatement = + this.bigQueryConnection.prepareCall( + "call testProc('?')", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.CLOSE_CURSORS_AT_COMMIT); + assertNotNull(callableStatement); + callableStatement.registerOutParameter("ParamKey", Types.NUMERIC, 2); + callableStatement.close(); + } + + // Integration tests for CallableStatement Setters and Getters + @Test + public void testSetterGetterBigDecimal() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + BigDecimal expected = new BigDecimal(12344); + callableStatement.setBigDecimal(CALLABLE_STMT_PARAM_KEY, expected); + BigDecimal actual = callableStatement.getBigDecimal(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterBoolean() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Boolean expected = true; + callableStatement.setBoolean(CALLABLE_STMT_PARAM_KEY, expected); + Boolean actual = callableStatement.getBoolean(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterByte() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Byte expected = "hello".getBytes()[0]; + callableStatement.setByte(CALLABLE_STMT_PARAM_KEY, expected); + Byte actual = callableStatement.getByte(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterBytes() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + byte[] expected = "hello".getBytes(); + callableStatement.setBytes(CALLABLE_STMT_PARAM_KEY, expected); + byte[] actual = callableStatement.getBytes(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterDate() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Date expected = new Date(1234567); + callableStatement.setDate(CALLABLE_STMT_PARAM_KEY, expected); + Date actual = callableStatement.getDate(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterDateCal() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Date expected = new Date(1L); + Calendar cal = Calendar.getInstance(); + callableStatement.setDate(CALLABLE_STMT_PARAM_KEY, expected, cal); + Date actual = callableStatement.getDate(CALLABLE_STMT_PARAM_KEY, cal); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterDouble() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Double expected = 123.2345; + callableStatement.setDouble(CALLABLE_STMT_PARAM_KEY, expected); + Double actual = callableStatement.getDouble(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterFloat() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Float expected = 123.2345F; + callableStatement.setFloat(CALLABLE_STMT_PARAM_KEY, expected); + Float actual = callableStatement.getFloat(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterInt() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Integer expected = 123; + callableStatement.setInt(CALLABLE_STMT_PARAM_KEY, expected); + Integer actual = callableStatement.getInt(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterLong() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Long expected = 123L; + callableStatement.setLong(CALLABLE_STMT_PARAM_KEY, expected); + Long actual = callableStatement.getLong(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterNString() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + String expected = "heelo"; + callableStatement.setNString(CALLABLE_STMT_PARAM_KEY, expected); + String actual = callableStatement.getNString(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterObject() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + String expected = "heelo"; + callableStatement.setObject(CALLABLE_STMT_PARAM_KEY, expected); + Object actual = callableStatement.getObject(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterObjectWithSQLType() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + String expected = "heelo"; + callableStatement.setObject(CALLABLE_STMT_PARAM_KEY, expected, Types.NVARCHAR); + Object actual = callableStatement.getObject(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterObjectWithSqlTypeAndScale() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + String expected = "heelo"; + callableStatement.setObject(CALLABLE_STMT_PARAM_KEY, expected, Types.NVARCHAR, 0); + Object actual = callableStatement.getObject(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterString() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + String expected = "123"; + callableStatement.setString(CALLABLE_STMT_PARAM_KEY, expected); + String actual = callableStatement.getString(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterTime() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Time expected = new Time(1234567); + callableStatement.setTime(CALLABLE_STMT_PARAM_KEY, expected); + Time actual = callableStatement.getTime(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterTimeCal() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Time expected = new Time(1L); + Calendar cal = Calendar.getInstance(); + callableStatement.setTime(CALLABLE_STMT_PARAM_KEY, expected, cal); + Time actual = callableStatement.getTime(CALLABLE_STMT_PARAM_KEY, cal); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterTimestamp() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Timestamp expected = new Timestamp(1234567); + callableStatement.setTimestamp(CALLABLE_STMT_PARAM_KEY, expected); + Timestamp actual = callableStatement.getTimestamp(CALLABLE_STMT_PARAM_KEY); + assertEquals(expected, actual); + } + + @Test + public void testSetterGetterTimestampCal() throws SQLException { + CallableStatement callableStatement = this.bigQueryConnection.prepareCall("call testProc('?')"); + assertNotNull(callableStatement); + Timestamp expected = new Timestamp(1L); + Calendar cal = Calendar.getInstance(); + callableStatement.setTimestamp(CALLABLE_STMT_PARAM_KEY, expected, cal); + Timestamp actual = callableStatement.getTimestamp(CALLABLE_STMT_PARAM_KEY, cal); + assertEquals(expected, actual); + } + + @Test + public void testAdditionalProjectsInMetadata() throws SQLException { + String additionalProjectsValue = "bigquery-public-data"; + String datasetInAdditionalProject = "baseball"; + + String urlWithAdditionalProjects = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=" + + PROJECT_ID + + ";OAuthType=3" + + ";AdditionalProjects=" + + additionalProjectsValue; + + try (Connection conn = DriverManager.getConnection(urlWithAdditionalProjects)) { + DatabaseMetaData dbMetaData = conn.getMetaData(); + + // 1. Test getCatalogs() + Set foundCatalogs = new HashSet<>(); + try (ResultSet catalogsRs = dbMetaData.getCatalogs()) { + while (catalogsRs.next()) { + foundCatalogs.add(catalogsRs.getString("TABLE_CAT")); + } + } + assertTrue( + "getCatalogs() should contain the primary project ID", + foundCatalogs.contains(PROJECT_ID)); + assertTrue( + "getCatalogs() should contain the additional project ID", + foundCatalogs.contains(additionalProjectsValue)); + + // 2. Test getSchemas() + Set catalogsForSchemasFromAll = new HashSet<>(); + boolean foundAdditionalDataset = false; + try (ResultSet schemasRs = dbMetaData.getSchemas()) { + while (schemasRs.next()) { + String schemaName = schemasRs.getString("TABLE_SCHEM"); + String catalogName = schemasRs.getString("TABLE_CATALOG"); + catalogsForSchemasFromAll.add(catalogName); + if (additionalProjectsValue.equals(catalogName) + && datasetInAdditionalProject.equals(schemaName)) { + foundAdditionalDataset = true; + } + } + } + assertTrue( + "getSchemas() should list datasets from the primary project", + catalogsForSchemasFromAll.contains(PROJECT_ID)); + assertTrue( + "getSchemas() should list datasets from the additional project", + catalogsForSchemasFromAll.contains(additionalProjectsValue)); + assertTrue( + "Known dataset from additional project not found in getSchemas()", + foundAdditionalDataset); + + } catch (SQLException e) { + System.err.println("SQL Error during AdditionalProjects test: " + e.getMessage()); + throw e; + } + } + + @Test + public void testFilterTablesOnDefaultDataset_getTables() throws SQLException { + String defaultDatasetValue = CONSTRAINTS_DATASET; + String table1InDefaultDataset = CONSTRAINTS_TABLE_NAME; + String table2InDefaultDataset = CONSTRAINTS_TABLE_NAME2; + + String specificDatasetValue = "JDBC_TABLE_TYPES_TEST"; + String table1InSpecificDataset = "base_table"; + String table2InSpecificDataset = "external_table"; + + String connectionUrl = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=" + + PROJECT_ID + + ";OAuthType=3" + + ";DefaultDataset=" + + defaultDatasetValue + + ";FilterTablesOnDefaultDataset=1"; + try (Connection conn = DriverManager.getConnection(connectionUrl)) { + DatabaseMetaData dbMetaData = conn.getMetaData(); + + // Case 1: Catalog and schemaPattern are null/wildcard, should use DefaultDataset + try (ResultSet rs = dbMetaData.getTables(null, null, null, null)) { + Set tableNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); + tableNames.add(rs.getString("TABLE_NAME")); + } + assertTrue(tableNames.contains(table1InDefaultDataset)); + assertTrue(tableNames.contains(table2InDefaultDataset)); + } + + // Case 2: Explicit schemaPattern overrides DefaultDataset + try (ResultSet rs = dbMetaData.getTables(null, specificDatasetValue, null, null)) { + Set tableNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); + tableNames.add(rs.getString("TABLE_NAME")); + } + assertTrue(tableNames.contains(table1InSpecificDataset)); + assertTrue(tableNames.contains(table2InSpecificDataset)); + } + + // Case 3: Explicit catalog, schemaPattern is null/wildcard, should use DefaultDataset within + // that catalog + try (ResultSet rs = dbMetaData.getTables(PROJECT_ID, null, null, null)) { + Set tableNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); + tableNames.add(rs.getString("TABLE_NAME")); + } + assertTrue(tableNames.contains(table1InDefaultDataset)); + assertTrue(tableNames.contains(table2InDefaultDataset)); + } + + // Case 4: Explicit catalog and schemaPattern override DefaultDataset + try (ResultSet rs = dbMetaData.getTables(PROJECT_ID, specificDatasetValue, null, null)) { + Set tableNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); + tableNames.add(rs.getString("TABLE_NAME")); + } + assertTrue(tableNames.contains(table1InSpecificDataset)); + assertTrue(tableNames.contains(table2InSpecificDataset)); + } + } + } + + @Test + public void testFilterTablesOnDefaultDataset_getColumns() throws SQLException { + String defaultDatasetValue = CONSTRAINTS_DATASET; + String tableInDefaultDataset = CONSTRAINTS_TABLE_NAME; + String[] columnsInDefaultTable = {"id", "name", "second_name", "address"}; + + String specificDatasetValue = "JDBC_TABLE_TYPES_TEST"; + String tableInSpecificDataset = "base_table"; + String[] columnsInSpecificTable = {"id", "name", "created_at"}; + + String connectionUrl = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=" + + PROJECT_ID + + ";OAuthType=3" + + ";DefaultDataset=" + + defaultDatasetValue + + ";FilterTablesOnDefaultDataset=1"; + + try (Connection conn = DriverManager.getConnection(connectionUrl)) { + DatabaseMetaData dbMetaData = conn.getMetaData(); + + // Case 1: Catalog and schemaPattern are null/wildcard, should use DefaultDataset + try (ResultSet rs = dbMetaData.getColumns(null, null, tableInDefaultDataset, null)) { + Set columnNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); + assertEquals(tableInDefaultDataset, rs.getString("TABLE_NAME")); + columnNames.add(rs.getString("COLUMN_NAME")); + } + for (String expectedCol : columnsInDefaultTable) { + assertTrue(columnNames.contains(expectedCol)); + } + assertEquals(columnsInDefaultTable.length, columnNames.size()); + } + + // Case 2: Explicit schemaPattern overrides DefaultDataset + try (ResultSet rs = + dbMetaData.getColumns(null, specificDatasetValue, tableInSpecificDataset, null)) { + Set columnNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); + assertEquals(tableInSpecificDataset, rs.getString("TABLE_NAME")); + columnNames.add(rs.getString("COLUMN_NAME")); + } + for (String expectedCol : columnsInSpecificTable) { + assertTrue(columnNames.contains(expectedCol)); + } + assertEquals(columnsInSpecificTable.length, columnNames.size()); + } + + // Case 3: Explicit catalog, schemaPattern is null/wildcard, should use DefaultDataset within + // that catalog + try (ResultSet rs = dbMetaData.getColumns(PROJECT_ID, null, tableInDefaultDataset, null)) { + Set columnNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(defaultDatasetValue, rs.getString("TABLE_SCHEM")); + assertEquals(tableInDefaultDataset, rs.getString("TABLE_NAME")); + columnNames.add(rs.getString("COLUMN_NAME")); + } + for (String expectedCol : columnsInDefaultTable) { + assertTrue(columnNames.contains(expectedCol)); + } + assertEquals(columnsInDefaultTable.length, columnNames.size()); + } + + // Case 4: Explicit catalog and schemaPattern override DefaultDataset + try (ResultSet rs = + dbMetaData.getColumns(PROJECT_ID, specificDatasetValue, tableInSpecificDataset, null)) { + Set columnNames = new HashSet<>(); + while (rs.next()) { + assertEquals(PROJECT_ID, rs.getString("TABLE_CAT")); + assertEquals(specificDatasetValue, rs.getString("TABLE_SCHEM")); + assertEquals(tableInSpecificDataset, rs.getString("TABLE_NAME")); + columnNames.add(rs.getString("COLUMN_NAME")); + } + for (String expectedCol : columnsInSpecificTable) { + assertTrue(columnNames.contains(expectedCol)); + } + assertEquals(columnsInSpecificTable.length, columnNames.size()); + } + } + } + + @Test + public void testAlterTable() throws SQLException { + String TABLE_NAME = "JDBC_ALTER_TABLE_" + randomNumber; + String createQuery = + String.format("CREATE OR REPLACE TABLE %s.%s (`StringField` STRING);", DATASET, TABLE_NAME); + String addColumnQuery = + String.format("ALTER TABLE %s.%s ADD COLUMN `IntegerField` INTEGER;", DATASET, TABLE_NAME); + String dropColumnQuery = + String.format( + "UPDATE %s.%s SET StringField='Jane Doe' WHERE IntegerField=111", DATASET, TABLE_NAME); + String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME); + String selectQuery = String.format("SELECT * FROM %s.%s", DATASET, TABLE_NAME); + + int createStatus = bigQueryStatement.executeUpdate(createQuery); + assertEquals(0, createStatus); + + int addColumnStatus = bigQueryStatement.executeUpdate(addColumnQuery); + assertEquals(0, addColumnStatus); + + bigQueryStatement.executeQuery(selectQuery); + int selectStatus = bigQueryStatement.getUpdateCount(); + assertEquals(-1, selectStatus); + + int dropColumnStatus = bigQueryStatement.executeUpdate(dropColumnQuery); + assertEquals(0, dropColumnStatus); + + int dropStatus = bigQueryStatement.executeUpdate(dropQuery); + assertEquals(0, dropStatus); + + bigQueryStatement.execute(String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TABLE_NAME)); + } + + @Test + public void testValidLegacySQLStatement() throws SQLException { + String legacyJoinQuery = + "SELECT\n" + + " repo_name\n" + + "FROM\n" + + " [bigquery-public-data.github_repos.commits],\n" + + " [bigquery-public-data.github_repos.sample_commits] LIMIT 10"; + String connection_uri = + "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;" + + "OAuthType=3;ProjectId=" + + PROJECT_ID + + ";QueryDialect=BIG_QUERY;"; + Connection connection = DriverManager.getConnection(connection_uri); + Statement statement = connection.createStatement(); + + boolean result = statement.execute(legacyJoinQuery); + assertTrue(result); + connection.close(); + } + + @Test + public void testConnectionWithMultipleTransactionCommits() throws SQLException { + String TRANSACTION_TABLE = "JDBC_MULTI_COMMIT_TABLE" + randomNumber; + String createTransactionTable = + String.format( + "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);", + DATASET, TRANSACTION_TABLE); + String insertQuery = + String.format( + "INSERT INTO %s.%s (id, name, age) VALUES (12, 'DwightShrute', %s);", + DATASET, TRANSACTION_TABLE, randomNumber); + String updateQuery = + String.format( + "UPDATE %s.%s SET age = 14 WHERE age = %s;", DATASET, TRANSACTION_TABLE, randomNumber); + String selectQuery = + String.format("SELECT id, name, age FROM %s.%s WHERE id = 12;", DATASET, TRANSACTION_TABLE); + + bigQueryStatement.execute(createTransactionTable); + + Connection connection = DriverManager.getConnection(session_enabled_connection_uri); + connection.setAutoCommit(false); + + Statement statement = connection.createStatement(); + statement.execute(insertQuery); + statement.execute(updateQuery); + connection.commit(); // First transaction + + // After commit, a new transaction should have started. + // Executing another query and then rolling it back. + String insertQuery2 = + String.format( + "INSERT INTO %s.%s (id, name, age) VALUES (15, 'MichaelScott', 25);", + DATASET, TRANSACTION_TABLE); + statement.execute(insertQuery2); + connection.rollback(); // Second transaction + + // Verify state with the static bigQueryStatement + ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery); + int count = 0; + while (resultSet.next()) { + count++; + assertEquals(14, resultSet.getInt("age")); + } + assertEquals(1, count); // Only first transaction should be committed. + + // Verify the second insert was rolled back + ResultSet rs2 = + bigQueryStatement.executeQuery( + String.format("SELECT * FROM %s.%s WHERE id=15", DATASET, TRANSACTION_TABLE)); + assertFalse(rs2.next()); + + bigQueryStatement.execute( + String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE)); + + statement.close(); + connection.close(); + } + + // Private Helper functions + private String getSessionId() throws InterruptedException { + QueryJobConfiguration stubJobConfig = + QueryJobConfiguration.newBuilder("Select 1;").setCreateSession(true).build(); + Job job = bigQuery.create(JobInfo.of(stubJobConfig)); + job = job.waitFor(); + Job stubJob = bigQuery.getJob(job.getJobId()); + return stubJob.getStatistics().getSessionInfo().getSessionId(); + } + + @Test + public void testCallableStatementScriptExecuteUpdate() throws SQLException { + int randomNum = java.util.UUID.randomUUID().hashCode(); + String insertName = "callable-statement-dml-insert-test"; + String insertResult = String.format("%s-%d", insertName, randomNum); + String updateName = "callable-statement-dml-update-test"; + String updateResult = String.format("%s-%d", updateName, randomNum); + String selectStmtQuery = + String.format("SELECT * FROM %s.%s WHERE id = ?", DATASET, CALLABLE_STMT_DML_TABLE_NAME); + String insertCallStmtQuery = + String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_INSERT_PROC_NAME); + String updateCallStmtQuery = + String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_UPDATE_PROC_NAME); + String deleteCallStmtQuery = + String.format("CALL %s.%s(?);", DATASET, CALLABLE_STMT_DML_DELETE_PROC_NAME); + + // DML INSERT + CallableStatement callableStatement = bigQueryConnection.prepareCall(insertCallStmtQuery); + assertNotNull(callableStatement); + callableStatement.setString(1, insertName); + callableStatement.setInt(2, randomNum); + callableStatement.setString(3, insertResult); + int rowsInserted = callableStatement.executeUpdate(); + assertEquals(1, rowsInserted); + + PreparedStatement preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); + assertNotNull(preparedStatement); + preparedStatement.setInt(1, randomNum); + ResultSet rs = preparedStatement.executeQuery(); + assertNotNull(rs); + assertTrue(rs.next()); + + assertEquals(insertName, rs.getString(1)); + assertEquals(randomNum, rs.getInt(2)); + assertEquals(insertResult, rs.getString(3)); + + // DML UPDATE + callableStatement = bigQueryConnection.prepareCall(updateCallStmtQuery); + assertNotNull(callableStatement); + callableStatement.setString(1, updateName); + callableStatement.setInt(2, randomNum); + callableStatement.setString(3, updateResult); + int rowsUpdated = callableStatement.executeUpdate(); + assertEquals(1, rowsUpdated); + + preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); + assertNotNull(preparedStatement); + preparedStatement.setInt(1, randomNum); + rs = preparedStatement.executeQuery(); + assertNotNull(rs); + assertTrue(rs.next()); + + assertEquals(updateName, rs.getString(1)); + assertEquals(randomNum, rs.getInt(2)); + assertEquals(updateResult, rs.getString(3)); + + // DML DELETE + callableStatement = bigQueryConnection.prepareCall(deleteCallStmtQuery); + assertNotNull(callableStatement); + callableStatement.setInt(1, randomNum); + int rowsDeleted = callableStatement.executeUpdate(); + assertEquals(1, rowsDeleted); + + preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); + assertNotNull(preparedStatement); + preparedStatement.setInt(1, randomNum); + rs = preparedStatement.executeQuery(); + assertNotNull(rs); + assertFalse(rs.next()); + + callableStatement.close(); + } + + @Test + public void testCallableStatementScriptExecuteLargeUpdate() throws SQLException { + int randomNum = java.util.UUID.randomUUID().hashCode(); + String insertName = "callable-statement-dml-insert-test"; + String insertResult = String.format("%s-%d", insertName, randomNum); + String updateName = "callable-statement-dml-update-test"; + String updateResult = String.format("%s-%d", updateName, randomNum); + String selectStmtQuery = + String.format("SELECT * FROM %s.%s WHERE id = ?", DATASET, CALLABLE_STMT_DML_TABLE_NAME); + String insertCallStmtQuery = + String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_INSERT_PROC_NAME); + String updateCallStmtQuery = + String.format("CALL %s.%s(?,?,?);", DATASET, CALLABLE_STMT_DML_UPDATE_PROC_NAME); + String deleteCallStmtQuery = + String.format("CALL %s.%s(?);", DATASET, CALLABLE_STMT_DML_DELETE_PROC_NAME); + + // DML INSERT + CallableStatement callableStatement = bigQueryConnection.prepareCall(insertCallStmtQuery); + assertNotNull(callableStatement); + callableStatement.setString(1, insertName); + callableStatement.setInt(2, randomNum); + callableStatement.setString(3, insertResult); + long rowsInserted = callableStatement.executeLargeUpdate(); + assertEquals(1L, rowsInserted); + + PreparedStatement preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); + assertNotNull(preparedStatement); + preparedStatement.setInt(1, randomNum); + ResultSet rs = preparedStatement.executeQuery(); + assertNotNull(rs); + assertTrue(rs.next()); + + assertEquals(insertName, rs.getString(1)); + assertEquals(randomNum, rs.getInt(2)); + assertEquals(insertResult, rs.getString(3)); + + // DML UPDATE + callableStatement = bigQueryConnection.prepareCall(updateCallStmtQuery); + assertNotNull(callableStatement); + callableStatement.setString(1, updateName); + callableStatement.setInt(2, randomNum); + callableStatement.setString(3, updateResult); + long rowsUpdated = callableStatement.executeLargeUpdate(); + assertEquals(1L, rowsUpdated); + + preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); + assertNotNull(preparedStatement); + preparedStatement.setInt(1, randomNum); + rs = preparedStatement.executeQuery(); + assertNotNull(rs); + assertTrue(rs.next()); + + assertEquals(updateName, rs.getString(1)); + assertEquals(randomNum, rs.getInt(2)); + assertEquals(updateResult, rs.getString(3)); + + // DML DELETE + callableStatement = bigQueryConnection.prepareCall(deleteCallStmtQuery); + assertNotNull(callableStatement); + callableStatement.setInt(1, randomNum); + long rowsDeleted = callableStatement.executeLargeUpdate(); + assertEquals(1L, rowsDeleted); + + preparedStatement = bigQueryConnection.prepareStatement(selectStmtQuery); + assertNotNull(preparedStatement); + preparedStatement.setInt(1, randomNum); + rs = preparedStatement.executeQuery(); + assertNotNull(rs); + assertFalse(rs.next()); + + callableStatement.close(); + } + + @Test + public void testScript() throws SQLException { + String BASE_QUERY = + "SELECT * FROM bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2017 order by" + + " trip_distance asc LIMIT %s;"; + String query1 = String.format(BASE_QUERY, 5000); + String query2 = String.format(BASE_QUERY, 7000); + String query3 = String.format(BASE_QUERY, 9000); + + bigQueryStatement.execute(query1 + query2 + query3); + ResultSet resultSet = bigQueryStatement.getResultSet(); + assertEquals(5000, resultSetRowCount(resultSet)); + + boolean hasMoreResult = bigQueryStatement.getMoreResults(); + assertTrue(hasMoreResult); + resultSet = bigQueryStatement.getResultSet(); + assertEquals(7000, resultSetRowCount(resultSet)); + + hasMoreResult = bigQueryStatement.getMoreResults(); + assertTrue(hasMoreResult); + resultSet = bigQueryStatement.getResultSet(); + assertEquals(9000, resultSetRowCount(resultSet)); + } + + @Test + public void testCallableStatementScriptExecute() throws SQLException { + int randomNum = random.nextInt(99); + String callableStmtQuery = + String.format( + "DECLARE call_result STRING;" + + "CALL %s.%s(?,?,call_result);" + + "SELECT * FROM %s.%s WHERE result = call_result;", + DATASET, CALLABLE_STMT_PROC_NAME, DATASET, CALLABLE_STMT_TABLE_NAME); + CallableStatement callableStatement = bigQueryConnection.prepareCall(callableStmtQuery); + callableStatement.setString(1, "callable-stmt-test"); + callableStatement.setInt(2, randomNum); + + assertFalse(callableStatement.execute()); + assertEquals(1, callableStatement.getUpdateCount()); + + // This is an actual SELECT * from the above + assertTrue(callableStatement.getMoreResults()); + ResultSet resultSet = callableStatement.getResultSet(); + ResultSetMetaData rsMetadata = resultSet.getMetaData(); + assertEquals(3, rsMetadata.getColumnCount()); + + assertTrue(resultSet.next()); + + String expected = String.format("callable-stmt-test-%d", randomNum); + String actual = resultSet.getString(3); + + assertEquals(expected, actual); + + // Validate there are no more results + assertFalse(callableStatement.getMoreResults()); + assertEquals(-1, callableStatement.getUpdateCount()); + callableStatement.close(); + } + + @Test + public void testExecuteScriptWithExpession() throws SQLException { + int randomNum = random.nextInt(99); + String query = String.format("DECLARE x INT64; SET x = (SELECT %s); SELECT x;", randomNum); + + assertTrue(bigQueryStatement.execute(query)); + ResultSet rs = bigQueryStatement.getResultSet(); + assertTrue(rs.next()); + assertEquals(randomNum, rs.getInt(1)); + assertFalse(rs.next()); + assertFalse(bigQueryStatement.getMoreResults()); + assertEquals(-1, bigQueryStatement.getUpdateCount()); + } + + @Test + public void testInformationSchemaTables() throws SQLException { + String query = String.format("SELECT * FROM %s.INFORMATION_SCHEMA.TABLES", DATASET); + try (Statement statement = bigQueryConnection.createStatement(); + ResultSet resultSet = statement.executeQuery(query)) { + ResultSetMetaData metaData = resultSet.getMetaData(); + int columnCount = metaData.getColumnCount(); + assertTrue(columnCount > 0); + int rowCount = 0; + while (resultSet.next()) { + rowCount++; + for (int i = 1; i <= columnCount; i++) { + Object obj = resultSet.getObject(i); + if (obj != null) { + assertNotNull(obj.toString()); + } + } + } + assertTrue(rowCount > 0); + } + } + + private void validate( + String method, + BiFunction getter, + ImmutableMap expectedResult) + throws Exception { + + try (Connection connection = DriverManager.getConnection(connection_uri); + Connection connectionHTAPI = + DriverManager.getConnection( + connection_uri + + ";HighThroughputMinTableSize=0;HighThroughputActivationRatio=0;EnableHighThroughputAPI=1;"); + Statement statement = connection.createStatement(); + Statement statementHTAPI = connectionHTAPI.createStatement()) { + + String query = + "SELECT * FROM INTEGRATION_TEST_FORMAT.all_bq_types WHERE stringField is not null"; + ResultSet resultSetRegular = statement.executeQuery(query); + ResultSet resultSetArrow = statementHTAPI.executeQuery(query); + resultSetRegular.next(); + resultSetArrow.next(); + + for (int i = 1; i <= resultSetRegular.getMetaData().getColumnCount(); i++) { + String columnName = resultSetRegular.getMetaData().getColumnName(i); + + String regularApiLabel = + String.format("[Method: %s] [Column: %s] [API: Regular]", method, columnName); + String htapiApiLabel = + String.format("[Method: %s] [Column: %s] [API: HTAPI]", method, columnName); + + if (expectedResult.containsKey(columnName)) { + Object expectedValue = expectedResult.get(columnName); + + assertEquals(regularApiLabel, expectedValue, getter.apply(resultSetRegular, i)); + assertEquals(htapiApiLabel, expectedValue, getter.apply(resultSetArrow, i)); + + } else { + String regularMsg = "Expected exception but got a value. " + regularApiLabel; + assertEquals(regularMsg, EXCEPTION_REPLACEMENT, getter.apply(resultSetRegular, i)); + + String htapiMsg = "Expected exception but got a value. " + htapiApiLabel; + assertEquals(htapiMsg, EXCEPTION_REPLACEMENT, getter.apply(resultSetArrow, i)); + } + } + } + } + + @Test + public void validateGetString() throws Exception { + final ImmutableMap stringResults = + new ImmutableMap.Builder() + .put("stringField", "StringValue") + .put("bytesField", "Qnl0ZXNWYWx1ZQ==") + .put("intField", "123") + .put("floatField", "10.5") + .put("numericField", "12345.67") + .put("bigNumericField", "98765432109876543210.123456789") + .put("booleanField", "true") + .put("timestampFiled", "2023-07-28 12:30:00.000000") + .put("dateField", "2023-07-28") + .put("timeField", "12:30:00.000") + .put("dateTimeField", "2023-07-28 12:30:00.000000") + .put("geographyField", "POINT(-74.006 40.7128)") + .put( + "recordField", + "{\"name\":\"NameValue\",\"recordNested\":{\"lastName\":\"LastNameValue\"}}") + .put("rangeField", "[2023-01-01, 2023-12-01)") + .put("jsonField", "{\"key\":\"value\"}") + .put("arrayString", "[abc, def, ghi]") + .put("arrayRecord", "[{\"value\":\"rec_val1\"}, {\"value\":\"rec_val2\"}]") + .put("arrayBytes", "[Ynl0ZTE=, Ynl0ZTI=]") + .put("arrayInteger", "[10, 20]") + .put("arrayNumeric", "[10.5, 20.5]") + .put("arrayBignumeric", "[100.1, 200.2]") + .put("arrayBoolean", "[true, false]") + .put("arrayTimestamp", "[2023-01-01 01:00:00.0, 2023-01-01 02:00:00.0]") + .put("arrayDate", "[2023-01-01, 2023-01-02]") + .put("arrayTime", "[01:00:00, 02:00:00]") + .put("arrayDatetime", "[2023-01-01 01:00:00.0, 2023-01-01 02:00:00.0]") + .put("arrayGeography", "[POINT(1 1), POINT(2 2)]") + .put("arrayRange", "[[2023-01-01, 2023-01-03), [2023-01-04, 2023-01-06)]") + .put("arrayJson", "[{\"a\":1}, {\"b\":2}]") + .put("arrayFloat", "[1.1, 2.2]") + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getString(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getString", getter, stringResults); + } + + @Test + public void validateGetInt() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("intField", 123) + .put("floatField", 10) + .put("numericField", 12345) + .put("booleanField", 1) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getInt(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getInt", getter, result); + } + + @Test + public void validateGetLong() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("intField", 123L) + .put("floatField", 10L) + .put("numericField", 12345L) + .put("booleanField", 1L) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getLong(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getLong", getter, result); + } + + @Test + public void validateGetBool() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("intField", true) + .put("floatField", true) + .put("numericField", true) + .put("booleanField", true) + .put("bigNumericField", true) + .put("stringField", false) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getBoolean(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getBool", getter, result); + } + + @Test + public void validateGetFloat() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("intField", (float) 123.0) + .put("floatField", (float) 10.5) + .put("numericField", (float) 12345.67) + .put("bigNumericField", (float) 98765432109876543210.123456789) + .put("booleanField", (float) 1.0) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getFloat(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getFloat", getter, result); + } + + @Test + public void validateGetDouble() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("intField", (double) 123.0) + .put("floatField", (double) 10.5) + .put("numericField", (double) 12345.67) + .put("bigNumericField", (double) 98765432109876543210.123456789) + .put("booleanField", (double) 1.0) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getDouble(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getDouble", getter, result); + } + + @Test + public void validateGetShort() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("intField", (short) 123) + .put("floatField", (short) 10) + .put("numericField", (short) 12345) + .put("booleanField", (short) 1) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getShort(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getShort", getter, result); + } + + @Test + public void validateGetTime() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("timeField", Time.valueOf("12:30:00")) + .put("dateTimeField", Time.valueOf("12:30:00")) + .put("timestampFiled", Time.valueOf("12:30:00")) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getTime(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getTime", getter, result); + } + + @Test + public void validateGetDate() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("dateField", Date.valueOf("2023-07-28")) + .put("dateTimeField", Date.valueOf("2023-07-28")) + .put("timestampFiled", Date.valueOf("2023-07-28")) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getDate(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getDate", getter, result); + } + + @Test + public void validateGetTimestamp() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("timeField", Timestamp.valueOf("1970-01-01 12:30:00")) + .put("dateField", Timestamp.valueOf("2023-07-28 00:00:00")) + .put("dateTimeField", Timestamp.valueOf("2023-07-28 12:30:00")) + .put("timestampFiled", Timestamp.valueOf("2023-07-28 12:30:00")) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getTimestamp(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getTimestamp", getter, result); + } + + @Test + public void validateGetByte() throws Exception { + final ImmutableMap result = + new ImmutableMap.Builder() + .put("intField", (byte) 123) + .put("booleanField", (byte) 1) + .put("floatField", (byte) 10) + .build(); + BiFunction getter = + (s, i) -> { + try { + return s.getByte(i); + } catch (Exception e) { + return EXCEPTION_REPLACEMENT; + } + }; + validate("getByte", getter, result); + } + + @Test + public void validateGetObjectNullValues() throws Exception { + try (Connection connection = DriverManager.getConnection(connection_uri); + Connection connectionHTAPI = + DriverManager.getConnection( + connection_uri + + ";HighThroughputMinTableSize=0;HighThroughputActivationRatio=0;EnableHighThroughputAPI=1;"); + Statement statement = connection.createStatement(); + Statement statementHTAPI = connectionHTAPI.createStatement()) { + + String query = + "SELECT * FROM INTEGRATION_TEST_FORMAT.all_bq_types WHERE stringField is null;"; + ResultSet resultSetRegular = statement.executeQuery(query); + ResultSet resultSetArrow = statementHTAPI.executeQuery(query); + resultSetRegular.next(); + resultSetArrow.next(); + + for (int i = 1; i <= resultSetRegular.getMetaData().getColumnCount(); i++) { + String columnName = resultSetRegular.getMetaData().getColumnName(i); + if (!columnName.contains("array")) { + assertNull(resultSetRegular.getObject(i)); + assertNull(resultSetArrow.getObject(i)); + } else { + assertEquals(resultSetRegular.getObject(i).toString(), "[]"); + assertEquals(resultSetArrow.getObject(i).toString(), "[]"); + } + } + } + } + + private int resultSetRowCount(ResultSet resultSet) throws SQLException { + int rowCount = 0; + while (resultSet.next()) { + rowCount++; + } + return rowCount; + } +}