diff --git a/.kokoro/build.sh b/.kokoro/build.sh
index b8524fbb94d8..81755953c827 100755
--- a/.kokoro/build.sh
+++ b/.kokoro/build.sh
@@ -282,6 +282,7 @@ case ${JOB_TYPE} in
[[ "$(basename "${dir}")" != "dependency-analyzer" ]] && \
[[ "$(basename "${dir}")" != "dependency-convergence-check" ]] && \
[[ "$(basename "${dir}")" != "unmanaged-dependency-check" ]] && \
+ [[ "$(basename "${dir}")" != "bigquery-external-jdbc-tests" ]] && \
[[ "$(basename "${dir}")" != "google-cloud-jar-parent" ]]; then
changed_modules+=("${dir}")
diff --git a/java-bigquery/.cloudbuild/jdbc_external.yaml b/java-bigquery/.cloudbuild/jdbc_external.yaml
new file mode 100644
index 000000000000..8d10b637bc4a
--- /dev/null
+++ b/java-bigquery/.cloudbuild/jdbc_external.yaml
@@ -0,0 +1,28 @@
+# Copyright 2026 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.
+# Github action job to test core java library features on
+# downstream client libraries before they are released.
+
+steps:
+- name: 'gcr.io/cloud-devrel-public-resources/java11'
+ entrypoint: 'bash'
+ args: ['java-bigquery/.cloudbuild/scripts/jdbc-external.sh', "${_VERSION}"]
+ secretEnv: ['SA_SECRET']
+availableSecrets:
+ secretManager:
+ - versionName: projects/$PROJECT_ID/secrets/GoogleJDBCServiceAccountSecret/versions/latest
+ env: 'SA_SECRET'
+options:
+ workerPool: 'projects/bigquery-devtools-drivers/locations/us-east1/workerPools/java-bigquery-jdbc-pool'
+ logging: CLOUD_LOGGING_ONLY
\ No newline at end of file
diff --git a/java-bigquery/.cloudbuild/scripts/jdbc-external.sh b/java-bigquery/.cloudbuild/scripts/jdbc-external.sh
new file mode 100644
index 000000000000..df1c31bc04b4
--- /dev/null
+++ b/java-bigquery/.cloudbuild/scripts/jdbc-external.sh
@@ -0,0 +1,20 @@
+ROOT_FOLDER=$(git rev-parse --show-toplevel)
+
+cd ${ROOT_FOLDER}
+source .kokoro/common.sh
+install_modules java-bigquery
+
+cd ${ROOT_FOLDER}/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests
+
+# This pom.xml is using export vars for install
+export JDBC_VERSION=$1;
+JDBC_FOLDER=jdbc-driver
+JDBC_ZIP_NAME="SimbaJDBCDriverforGoogleBigQuery42_${JDBC_VERSION}.zip"
+JDBC_JAR_NAME=GoogleBigQueryJDBC42.jar
+export JDBC_JAR_PATH="$(pwd)/${JDBC_FOLDER}/${JDBC_JAR_NAME}"
+
+mkdir -p ${JDBC_FOLDER}
+gsutil -m cp gs://bq-dev-tools-simba-drivers-testing/simba-jdbc/${JDBC_ZIP_NAME} ${JDBC_FOLDER}
+unzip -p ./${JDBC_FOLDER}/${JDBC_ZIP_NAME} ${JDBC_JAR_NAME} > ${JDBC_JAR_PATH}
+
+mvn -B -V -fae -q install
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/Dockerfile b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/Dockerfile
new file mode 100644
index 000000000000..4586a37d2c33
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/Dockerfile
@@ -0,0 +1,19 @@
+FROM gcr.io/cloud-devrel-public-resources/java11
+
+ARG JDBC_VERSION
+ARG JDBC_JAR_PATH
+
+ENV container_jdbc_jar_path=/jdbc/simba/GoogleBigQueryJDBC42.jar
+
+COPY ./pom.xml /src/pom.xml
+COPY ${JDBC_JAR_PATH} ${container_jdbc_jar_path}
+
+# Override SIMBA_JDBC_JAR_PATH with path inside the container
+ENV JDBC_VERSION=${JDBC_VERSION}
+ENV JDBC_JAR_PATH=${container_jdbc_jar_path}
+ENV JDBC_DOCKER_ENV=true
+
+WORKDIR /src
+RUN mvn install
+
+ENTRYPOINT []
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/Makefile b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/Makefile
new file mode 100644
index 000000000000..188895345ba8
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/Makefile
@@ -0,0 +1,62 @@
+# GOOGLE_APPLICATION_CREDENTIALS is required when running certain tests
+SECRET=`cat $(GOOGLE_APPLICATION_CREDENTIALS)`
+
+SIMBA_JDBC_VERSION=${_VERSION}
+SIMBA_ZIP_FILE="SimbaJDBCDriverforGoogleBigQuery42_$(SIMBA_JDBC_VERSION).zip"
+
+# Driver under test configuration
+JDBC_FOLDER=drivers
+JDBC_JAR_NAME=GoogleBigQueryJDBC42.jar
+JDBC_JAR_PATH="$(JDBC_FOLDER)/$(JDBC_JAR_NAME)"
+JDBC_VERSION=$(SIMBA_JDBC_VERSION)
+
+skipSurefire ?= true
+
+# Container name used during this test
+CONTAINER_NAME=jdbc_test
+
+# no indentation for ifndef\endif due to their evaluation before execution
+.check-env: |
+ifndef GOOGLE_APPLICATION_CREDENTIALS
+ $(error GOOGLE_APPLICATION_CREDENTIALS is required to run tests)
+endif
+
+# Important: By default, this command will skip unittests.
+# To include unit tests, run: make integration-test skipSurefire=false
+integration-test:
+ mvn -B -ntp \
+ -Penable-integration-tests \
+ -DtrimStackTrace=false \
+ -DskipSurefire=$(skipSurefire) \
+ -Dclirr.skip=true \
+ -Denforcer.skip=true \
+ -Dit.failIfNoSpecifiedTests=true \
+ -Dit.test=$(test) \
+ integration-test
+
+.docker-run: |
+ docker run -it \
+ -v $(GOOGLE_APPLICATION_CREDENTIALS):/auth/application_creds.json \
+ -e "GOOGLE_APPLICATION_CREDENTIALS=/auth/application_creds.json" \
+ -v "$(GOOGLE_APPLICATION_CREDENTIALS).p12":/auth/application_creds.p12 \
+ -v $(PWD):/src \
+ -e "SA_SECRET=$(SECRET)" \
+ -e "SA_SECRET_P12=/auth/application_creds.p12" \
+ $(CONTAINER_NAME) $(args)
+
+download-simba:
+ mkdir -p $(JDBC_FOLDER)
+ gsutil -m cp gs://bq-dev-tools-simba-drivers-testing/simba-jdbc/$(SIMBA_ZIP_FILE) $(JDBC_FOLDER)
+ unzip -p ./$(JDBC_FOLDER)/$(SIMBA_ZIP_FILE) $(JDBC_JAR_NAME) > $(JDBC_JAR_PATH)
+
+docker-build: |
+ docker build --build-arg JDBC_JAR_PATH=$(JDBC_JAR_PATH) --build-arg JDBC_VERSION=$(JDBC_VERSION) -t $(CONTAINER_NAME) .
+
+docker-test-authentication: .check-env |
+ $(MAKE) .docker-run args="mvn -Dtest=ITJdbcAuthenticationTest test"
+
+docker-test test: .check-env |
+ $(MAKE) .docker-run args="mvn test -Dtest=$(test)"
+
+docker-test-all: .check-env |
+ $(MAKE) .docker-run args="mvn test"
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/pom.xml b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/pom.xml
new file mode 100644
index 000000000000..36f57cef8b2e
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/pom.xml
@@ -0,0 +1,102 @@
+
+
+ 4.0.0
+
+ com.google.cloud
+ bigquery-external-jdbc-tests
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+ ${env.JDBC_VERSION}
+ ${env.JDBC_JAR_PATH}
+
+
+
+
+
+
+
+ com.google.cloud
+ libraries-bom
+ 26.58.0
+ pom
+ import
+
+
+
+
+
+ com.google.cloud
+ google-cloud-bigquery
+
+
+
+ com.google.cloud
+ JDBC
+ ${jdbc.version}
+ system
+ ${jdbc.jarPath}
+
+
+
+
+ com.google.cloud
+ google-cloud-core
+
+
+ org.apache.avro
+ avro
+ 1.11.4
+
+
+ junit
+ junit
+ test
+ 4.13.2
+
+
+ com.google.truth
+ truth
+ test
+ 1.1.3
+
+
+ org.mockito
+ mockito-core
+ test
+ 1.10.19
+
+
+ joda-time
+ joda-time
+ test
+ 2.14.0
+
+
+
+
+
+
+ docker
+
+
+ env.JDBC_DOCKER_ENV
+
+
+
+
+ /mvn/test-target
+
+
+
+
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/main/java/com/google/cloud/bigquery/jdbc/javatests/JdbcStarter.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/main/java/com/google/cloud/bigquery/jdbc/javatests/JdbcStarter.java
new file mode 100644
index 000000000000..13dcb0ab4e16
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/main/java/com/google/cloud/bigquery/jdbc/javatests/JdbcStarter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2026 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.
+ */
+
+import com.google.cloud.ServiceOptions;
+import java.sql.DriverManager;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class JdbcStarter {
+
+ public static void main(String[] args) throws SQLException {
+ final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ final String oauthType = "3"; // Google Application Credentials
+
+ String connectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId="
+ + DEFAULT_CATALOG
+ + ";OAuthType="
+ + oauthType
+ + ";Timeout=3600;";
+ try(Connection connection = DriverManager.getConnection(connectionUrl);
+ Statement statement = connection.createStatement()) {
+ statement.execute("SELECT 1;");
+ }
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/BaseDatabaseMetadata.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/BaseDatabaseMetadata.java
new file mode 100644
index 000000000000..4271bc19dcd1
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/BaseDatabaseMetadata.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2026 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.javatests;
+
+import static java.sql.Types.TIME;
+import static java.sql.Types.TIMESTAMP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BaseDatabaseMetadata {
+
+ public static int getSizeOfResultSet(ResultSet rs) throws SQLException {
+ int count = 0;
+ while (rs.next()) {
+ count++;
+ }
+ return count;
+ }
+
+ public static List getInfoBySQL(Connection connection, String sqlCmd)
+ throws SQLException {
+ List result = new ArrayList<>();
+ try {Statement st = connection.createStatement()) {
+ ResultSet rs = st.executeQuery(sqlCmd);
+ while (rs.next()) {
+ result.add(rs.getString(1));
+ }
+ } catch (SQLException e) {
+ throw e;
+ }
+ }
+ return result;
+ }
+
+ protected void verifyAllBooleanMethods(DatabaseMetaData metaData) throws SQLException {
+ assertFalse(metaData.allProceduresAreCallable()); // false
+ assertTrue(metaData.allTablesAreSelectable()); // true
+
+ assertFalse(metaData.nullsAreSortedHigh()); // false
+ assertTrue(metaData.nullsAreSortedLow()); // true
+ assertFalse(metaData.nullsAreSortedAtEnd()); // false
+ assertFalse(metaData.nullsAreSortedAtStart()); // false
+ assertTrue(metaData.nullPlusNonNullIsNull()); // true
+
+ assertFalse(metaData.usesLocalFiles()); // false
+ assertFalse(metaData.usesLocalFilePerTable()); // false
+ assertFalse(metaData.storesUpperCaseIdentifiers()); // false
+ assertFalse(metaData.storesLowerCaseIdentifiers()); // false
+ assertTrue(metaData.supportsMixedCaseQuotedIdentifiers());
+ assertFalse(metaData.storesUpperCaseQuotedIdentifiers()); // false
+ assertFalse(metaData.storesLowerCaseQuotedIdentifiers()); // false
+ assertFalse(metaData.storesMixedCaseQuotedIdentifiers()); // false
+
+ assertFalse(metaData.supportsAlterTableWithAddColumn()); // false
+ assertFalse(metaData.supportsAlterTableWithDropColumn()); // false
+ assertTrue(metaData.supportsColumnAliasing());
+ assertTrue(metaData.nullPlusNonNullIsNull());
+ assertFalse(metaData.supportsConvert()); // false
+ assertFalse(metaData.supportsConvert(TIME, TIMESTAMP)); // false
+ assertTrue(metaData.supportsTableCorrelationNames());
+ assertTrue(metaData.supportsExpressionsInOrderBy());
+ assertFalse(metaData.supportsOrderByUnrelated()); // false
+ assertTrue(metaData.supportsGroupBy());
+ assertFalse(metaData.supportsGroupByUnrelated()); // false
+ assertTrue(metaData.supportsGroupByBeyondSelect());
+
+ assertTrue(metaData.supportsMultipleTransactions());
+ assertFalse(metaData.supportsNonNullableColumns());
+ assertTrue(metaData.supportsMinimumSQLGrammar());
+ assertTrue(metaData.supportsCoreSQLGrammar());
+ assertFalse(metaData.supportsExtendedSQLGrammar());
+ assertTrue(metaData.supportsANSI92EntryLevelSQL());
+ assertFalse(metaData.supportsANSI92IntermediateSQL());
+ assertFalse(metaData.supportsANSI92FullSQL());
+ assertFalse(metaData.supportsFullOuterJoins());
+ assertFalse(metaData.supportsLimitedOuterJoins());
+ assertTrue(metaData.isCatalogAtStart());
+ assertTrue(metaData.supportsSchemasInDataManipulation());
+
+ assertTrue(metaData.supportsSchemasInTableDefinitions());
+ assertTrue(metaData.supportsSchemasInIndexDefinitions());
+ assertTrue(metaData.supportsSchemasInPrivilegeDefinitions());
+ assertTrue(metaData.supportsCatalogsInDataManipulation());
+ assertFalse(metaData.supportsPositionedDelete());
+ assertFalse(metaData.supportsPositionedUpdate());
+
+ assertFalse(metaData.supportsSelectForUpdate());
+ assertTrue(metaData.supportsStoredProcedures());
+ assertTrue(metaData.supportsSubqueriesInComparisons());
+ assertTrue(metaData.supportsSubqueriesInExists());
+ assertTrue(metaData.supportsSubqueriesInIns());
+ assertTrue(metaData.supportsSubqueriesInQuantifieds());
+ assertTrue(metaData.supportsCorrelatedSubqueries());
+ assertFalse(metaData.supportsOpenCursorsAcrossCommit());
+
+ assertFalse(metaData.supportsOpenCursorsAcrossRollback());
+ assertTrue(metaData.supportsOpenStatementsAcrossCommit());
+ assertTrue(metaData.supportsOpenStatementsAcrossRollback());
+
+ assertFalse(
+ metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED)); // f
+
+ assertFalse(
+ metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ)); // f
+ assertFalse(metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_NONE)); // f
+ assertFalse(metaData.supportsDifferentTableCorrelationNames()); // f
+
+ assertFalse(metaData.dataDefinitionIgnoredInTransactions());
+ assertFalse(metaData.doesMaxRowSizeIncludeBlobs());
+
+ assertTrue(metaData.supportsResultSetType(ResultSet.TYPE_FORWARD_ONLY)); // t
+ assertFalse(metaData.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE)); // f
+ assertFalse(metaData.supportsResultSetType(ResultSet.TYPE_SCROLL_SENSITIVE)); // f
+
+ assertTrue(
+ metaData.supportsResultSetConcurrency(
+ ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)); // t
+ assertFalse(
+ metaData.supportsResultSetConcurrency(
+ ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)); // f
+ assertFalse(
+ metaData.supportsResultSetConcurrency(
+ ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY));
+ assertFalse(
+ metaData.supportsResultSetConcurrency(
+ ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE));
+ assertFalse(
+ metaData.supportsResultSetConcurrency(
+ ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY));
+ assertFalse(
+ metaData.supportsResultSetConcurrency(
+ ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE));
+
+ assertFalse(metaData.ownUpdatesAreVisible(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.ownUpdatesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.ownUpdatesAreVisible(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.ownDeletesAreVisible(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.ownDeletesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.ownDeletesAreVisible(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.ownInsertsAreVisible(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.ownInsertsAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.ownInsertsAreVisible(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.othersUpdatesAreVisible(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.othersUpdatesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.othersUpdatesAreVisible(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.othersDeletesAreVisible(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.othersDeletesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.othersDeletesAreVisible(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.othersInsertsAreVisible(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.othersInsertsAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.othersInsertsAreVisible(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.updatesAreDetected(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.updatesAreDetected(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.updatesAreDetected(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.deletesAreDetected(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.deletesAreDetected(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.deletesAreDetected(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertFalse(metaData.insertsAreDetected(ResultSet.TYPE_FORWARD_ONLY));
+ assertFalse(metaData.insertsAreDetected(ResultSet.TYPE_SCROLL_INSENSITIVE));
+ assertFalse(metaData.insertsAreDetected(ResultSet.TYPE_SCROLL_SENSITIVE));
+
+ assertTrue(metaData.supportsBatchUpdates());
+ assertFalse(metaData.supportsSavepoints());
+ assertFalse(metaData.supportsNamedParameters());
+ assertFalse(metaData.supportsMultipleOpenResults());
+ assertFalse(metaData.supportsGetGeneratedKeys());
+
+ assertFalse(metaData.generatedKeyAlwaysReturned());
+ assertFalse(metaData.supportsIntegrityEnhancementFacility());
+ assertFalse(metaData.supportsDataDefinitionAndDataManipulationTransactions());
+ assertFalse(metaData.isReadOnly());
+ }
+
+ protected void verifyIntMethods(DatabaseMetaData metaData) throws SQLException {
+ assertEquals(0, metaData.getMaxBinaryLiteralLength()); // 0
+ assertEquals(0, metaData.getMaxCharLiteralLength()); // 0
+ assertEquals(0, metaData.getMaxColumnsInGroupBy()); // 0
+ assertEquals(0, metaData.getMaxColumnsInIndex()); // 0
+ assertEquals(0, metaData.getMaxColumnsInOrderBy()); // 0
+ assertEquals(0, metaData.getMaxColumnsInSelect()); // 0
+ assertEquals(0, metaData.getMaxConnections()); // 0
+ assertEquals(0, metaData.getMaxCursorNameLength()); // 0
+ assertEquals(0, metaData.getMaxIndexLength()); // 0
+ assertEquals(0, metaData.getMaxProcedureNameLength()); // 0
+ assertEquals(0, metaData.getMaxRowSize()); // 0
+ assertEquals(0, metaData.getMaxStatementLength()); // 0
+ assertEquals(0, metaData.getMaxStatements()); // 0
+ assertEquals(1000, metaData.getMaxTablesInSelect()); // 1000
+ assertEquals(0, metaData.getMaxUserNameLength()); // 0
+ }
+
+ protected void verifyAllStringMethods(DatabaseMetaData metaData, String procedure)
+ throws SQLException {
+ assertEquals("`", metaData.getIdentifierQuoteString());
+ assertTrue(procedure.equalsIgnoreCase(metaData.getProcedureTerm()));
+ }
+
+ protected void verifyGetPrimaryKeys(
+ Connection connection, DatabaseMetaData metaData, String dataset) throws SQLException {
+ final String jdbctesttable = "JDBCTESTPRIMARYKEY";
+ connection
+ .createStatement()
+ .execute(
+ "create or replace table "
+ + dataset
+ + "."
+ + jdbctesttable
+ + "(KEYCOLUMN int NOT NULL, SECONDCOLUMN string, THIRDCOLUMN timestamp);");
+ ResultSet resultSet =
+ metaData.getPrimaryKeys(JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable);
+ assertFalse(resultSet.next());
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ }
+
+ protected void verifyTestProcedure(DatabaseMetaData metaData, String expectedProcedure)
+ throws SQLException {
+ assertEquals(expectedProcedure, metaData.getProcedureTerm());
+ assertTrue(metaData.supportsStoredProcedures());
+ ResultSet resultSet;
+ resultSet = metaData.getProcedureColumns("%", "%", "%", "%");
+ assertEquals(0, getSizeOfResultSet(resultSet));
+ resultSet = metaData.getProcedures("%", "%", "%");
+ assertEquals(0, getSizeOfResultSet(resultSet));
+ }
+
+ protected void verifyDriverMetadata(
+ DatabaseMetaData metaData,
+ int expectedMajorVer,
+ int expectedMinorVersion,
+ String expectedDatabaseProductName,
+ String expectedDriverName,
+ Pattern versionPattern)
+ throws SQLException {
+
+ assertEquals(expectedMajorVer, metaData.getJDBCMajorVersion());
+ assertEquals(expectedMinorVersion, metaData.getJDBCMinorVersion());
+ assertEquals(expectedDatabaseProductName, metaData.getDatabaseProductName());
+ assertEquals(expectedDriverName, metaData.getDriverName());
+ String driverVersion = metaData.getDriverVersion();
+ Matcher m = versionPattern.matcher(driverVersion);
+ assertTrue(m.matches());
+ int majorVersion = metaData.getDriverMajorVersion();
+ int minorVersion = metaData.getDriverMinorVersion();
+ assertEquals(Integer.parseInt(m.group(1)), majorVersion);
+ assertEquals(Integer.parseInt(m.group(2)), minorVersion);
+ }
+
+ protected void verifyCatalogHelper(
+ DatabaseMetaData metaData,
+ String expectedCatalogSeparator,
+ String expectedCatalogTerm,
+ int expectedCatalogNameLength,
+ String defaultCatalog,
+ boolean isBeforeFirstSupported,
+ boolean isFirstSupported)
+ throws SQLException {
+ assertEquals(expectedCatalogSeparator, metaData.getCatalogSeparator());
+ // Simba BQ JDBC driver maps Catalogs to projects
+ assertEquals(expectedCatalogTerm, metaData.getCatalogTerm());
+ assertEquals(expectedCatalogNameLength, metaData.getMaxCatalogNameLength());
+
+ // This should return the project name of the connection
+ ResultSet resultSet = metaData.getCatalogs();
+
+ // Compares ResultSetMetadata.
+ // verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_CATALOGS);
+ if (isBeforeFirstSupported) {
+ assertTrue(resultSet.isBeforeFirst());
+ }
+
+ int count = 0;
+ boolean defaultCatalogFound = false;
+ while (resultSet.next()) {
+ if (count == 0 && isFirstSupported) {
+ assertTrue(resultSet.isFirst());
+ }
+ ++count;
+ if (defaultCatalog.equals(resultSet.getString(1))) {
+ defaultCatalogFound = true;
+ break;
+ }
+ }
+ assertTrue(defaultCatalogFound);
+ resultSet.close();
+ }
+
+ protected void verifySchemaHelper(
+ Connection connection,
+ DatabaseMetaData metaData,
+ String schemaTerm,
+ int maxSchemaNameLength,
+ boolean isBeforeFirstSupported,
+ boolean isFirstSupported)
+ throws SQLException {
+ // Simba BQ JDBC driver maps Table Datasets to Schemas
+ assertEquals(schemaTerm, metaData.getSchemaTerm());
+ assertEquals(maxSchemaNameLength, metaData.getMaxSchemaNameLength());
+ // This should return all the datasets in the project of the connection
+ ResultSet resultSet = metaData.getSchemas();
+ // Compares ResultSetMetadata.
+ // verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_CATALOGS);
+ if (isBeforeFirstSupported) {
+ assertTrue(resultSet.isBeforeFirst());
+ }
+
+ int count = 0;
+ Set allVisibleDatasets = new HashSet<>();
+ while (resultSet.next()) {
+ allVisibleDatasets.add(resultSet.getString(1));
+ if (count == 0 && isFirstSupported) {
+ assertTrue(resultSet.isFirst());
+ }
+ ++count;
+ }
+ assertTrue(count >= 1);
+ List allAccessibleDatasets =
+ getInfoBySQL(connection, "SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA;");
+ assertTrue(allVisibleDatasets.containsAll(allAccessibleDatasets));
+ assertTrue(count >= allAccessibleDatasets.size());
+ resultSet.close();
+ }
+
+ protected void verifyTableTypes(DatabaseMetaData metaData, Collection expectedTypes)
+ throws SQLException {
+ ResultSet resultSet = metaData.getTableTypes();
+ Set types = new HashSet<>();
+ while (resultSet.next()) {
+ String col = resultSet.getString(1);
+ types.add(col);
+ }
+ assertTrue(types.size() > 0);
+ assertTrue(types.containsAll(expectedTypes));
+ }
+
+ protected void verifyGetTables(Connection connection, DatabaseMetaData metaData, String dataset)
+ throws SQLException {
+ final String jdbctesttable = "JDBCTESTTABLE";
+ final String jdbctestview = "JDBCTESTVIEW";
+
+ Statement statement = connection.createStatement();
+ statement.execute(
+ String.format(
+ "create or replace table `%s.%s.%s` "
+ + "(INTCOLUMN int, STRINGCOLUMN string, TIMECOLUMN timestamp);",
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable));
+ statement.execute(
+ String.format(
+ "create or replace view `%s.%s.%s` " + " as select 1 as VIEWCOLUMN;",
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctestview));
+
+ ResultSet resultSet;
+
+ // Pattern match for table
+ resultSet =
+ metaData.getTables(
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, "%", new String[] {"TABLE"});
+ // verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_TABLES);
+ Set tables = new HashSet<>();
+ while (resultSet.next()) {
+ tables.add(resultSet.getString(3));
+ }
+ assertTrue(tables.contains("JDBCTESTTABLE"));
+
+ // exact match for tablename
+ resultSet =
+ metaData.getTables(
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable, new String[] {"TABLE"});
+ tables = new HashSet<>();
+ while (resultSet.next()) {
+ tables.add(resultSet.getString(3));
+ }
+ assertEquals(jdbctesttable, tables.iterator().next());
+
+ // Pattern match for view
+ resultSet =
+ metaData.getTables(
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, "%", new String[] {"VIEW"});
+ Set views = new HashSet<>();
+ while (resultSet.next()) {
+ views.add(resultSet.getString(3));
+ }
+ assertTrue(views.contains(jdbctestview));
+
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ connection.createStatement().execute("drop view if exists " + dataset + "." + jdbctestview);
+ }
+
+ // GetTablePrivileges not supported for JDBC and ODBC datasource.
+ // Returns empty resultset
+ protected void verifyGetTablePrivileges(
+ Connection connection, DatabaseMetaData metaData, String dataset) throws SQLException {
+ final String jdbctesttable = "JDBCTESTPRIVILEGETABLE";
+
+ Statement statement = connection.createStatement();
+ statement.execute(
+ String.format(
+ "create or replace table `%s.%s.%s` (INTCOLUMN int, STRINGCOLUMN string, TIMECOLUMN timestamp);",
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable));
+
+ ResultSet resultSet =
+ metaData.getTablePrivileges(JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable);
+ int cols = resultSet.getMetaData().getColumnCount();
+ assertTrue(cols > 0);
+ int count = 0;
+ while (resultSet.next()) {
+ for (int i = 1; i <= cols; i++) {
+ assertNotNull(resultSet.getMetaData().getColumnName(i));
+ assertNotNull(resultSet.getString(i));
+ }
+ ++count;
+ }
+ assertEquals(0, count);
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ }
+
+ protected void verifyGetColumnPrivileges(
+ Connection connection, DatabaseMetaData metaData, String dataset) throws SQLException {
+ final String jdbctesttable = "JDBCTESTPRIVILEGETABLE";
+
+ connection
+ .createStatement()
+ .execute(
+ "create or replace table "
+ + dataset
+ + "."
+ + jdbctesttable
+ + "(INTCOLUMN int, STRINGCOLUMN string, TIMECOLUMN timestamp);");
+
+ ResultSet resultSet =
+ metaData.getColumnPrivileges(
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable, "%");
+ int cols = resultSet.getMetaData().getColumnCount();
+ assertTrue(cols > 0);
+ int count = 0;
+ while (resultSet.next()) {
+ for (int i = 1; i <= cols; i++) {
+ assertNotNull(resultSet.getMetaData().getColumnName(i));
+ assertNotNull(resultSet.getString(i));
+ }
+ ++count;
+ }
+ assertEquals(0, count);
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ }
+
+ protected void verifyForeignKeys(Connection connection, DatabaseMetaData metaData, String dataset)
+ throws SQLException {
+ final String jdbctesttable = "JDBCTESTTABLEFOREIGNKEYS";
+
+ connection
+ .createStatement()
+ .execute(
+ "create or replace table "
+ + dataset
+ + "."
+ + jdbctesttable
+ + "(INTCOLUMN int, STRINGCOLUMN string, TIMECOLUMN timestamp);");
+
+ ResultSet resultSet =
+ metaData.getImportedKeys(JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable);
+ int cols = resultSet.getMetaData().getColumnCount();
+ assertTrue(cols > 0);
+ int count = 0;
+ while (resultSet.next()) {
+ for (int i = 1; i <= cols; i++) {
+ assertNotNull(resultSet.getMetaData().getColumnName(i));
+ assertNotNull(resultSet.getString(i));
+ }
+ ++count;
+ }
+ assertEquals(0, count);
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ }
+
+ protected void verifyGetColumns(Connection connection, DatabaseMetaData metaData, String dataset)
+ throws SQLException {
+ final String jdbctesttable = "JDBCTESTCOLUMNS";
+ Statement statement = connection.createStatement();
+ statement.execute(
+ String.format(
+ "create or replace table `%s.%s.%s` (INTCOLUMN int, STRINGCOLUMN string, TIMECOLUMN timestamp);",
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable));
+ ResultSet resultSet =
+ metaData.getColumns(
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable, "%COLUMN");
+ int cols = resultSet.getMetaData().getColumnCount();
+ assertTrue(cols > 0);
+ // Resultset is not empty
+ int count = 0;
+ while (resultSet.next()) {
+ for (int i = 1; i <= cols; i++) {
+ assertNotNull(resultSet.getMetaData().getColumnName(i));
+ }
+ ++count;
+ }
+ resultSet.close();
+ assertTrue(count > 0);
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ }
+
+ protected void verifyVersionColumns(
+ Connection connection,
+ DatabaseMetaData metaData,
+ String dataset,
+ int expectedNumCols,
+ Collection expectedColNames)
+ throws SQLException {
+ final String jdbctesttable = "JDBCTESTCOLUMNS";
+ connection
+ .createStatement()
+ .execute(
+ "create or replace table "
+ + dataset
+ + "."
+ + jdbctesttable
+ + "(INTCOLUMN int, STRINGCOLUMN string, TIMECOLUMN timestamp);");
+ ResultSet resultSet =
+ metaData.getVersionColumns(JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable);
+ int numCols = resultSet.getMetaData().getColumnCount();
+ assertEquals(expectedNumCols, numCols);
+ List colNames = new ArrayList<>();
+ for (int i = 1; i <= numCols; i++) {
+ colNames.add(resultSet.getMetaData().getColumnName(i));
+ }
+ assertTrue(colNames.containsAll(expectedColNames));
+ resultSet.close();
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ }
+
+ protected void verifyIndexInfo(Connection connection, DatabaseMetaData metaData, String dataset)
+ throws SQLException {
+ final String jdbctesttable = "JDBCTESTTABLE";
+ connection
+ .createStatement()
+ .execute(
+ "create or replace table "
+ + dataset
+ + "."
+ + jdbctesttable
+ + "(INTCOLUMN int, STRINGCOLUMN string, TIMECOLUMN timestamp);");
+ ResultSet rs =
+ metaData.getIndexInfo(
+ JdbcConnectionHelper.DEFAULT_CATALOG, dataset, jdbctesttable, false, false);
+ assertFalse(rs.next()); // no index defined.
+ rs.close();
+ connection.createStatement().execute("drop table if exists " + dataset + "." + jdbctesttable);
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/JdbcConnectionHelper.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/JdbcConnectionHelper.java
new file mode 100644
index 000000000000..f70f49d0ee3d
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/JdbcConnectionHelper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2026 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.javatests;
+
+import com.google.cloud.ServiceOptions;
+import com.google.cloud.bigquery.BigQuery;
+import com.google.cloud.bigquery.BigQueryOptions;
+import com.google.cloud.bigquery.QueryJobConfiguration;
+
+public class JdbcConnectionHelper {
+
+ public static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ public static String connectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId="
+ + DEFAULT_CATALOG
+ + ";OAuthType=3;Timeout=3600;";
+
+ public static final String createDatasetQuery =
+ "CREATE SCHEMA IF NOT EXISTS `%s.%s` OPTIONS(default_table_expiration_days = 5)";
+ public static final String dropSchema = "DROP SCHEMA IF EXISTS `%s.%s` CASCADE;";
+ public static final String createTableQuery =
+ "CREATE OR REPLACE TABLE "
+ + " `%s.%s.%s` "
+ + " (\n"
+ + "`StringField` STRING,\n"
+ + "`BytesField` BYTES,\n"
+ + "`IntegerField` INTEGER,\n"
+ + "`FloatField` FLOAT64,\n"
+ + "`NumericField` NUMERIC,\n"
+ + "`BigNumericField` BIGNUMERIC,\n"
+ + "`BooleanField` BOOLEAN,\n"
+ + "`TimestampField` TIMESTAMP,\n"
+ + "`DateField` DATE,\n"
+ + "`TimeField` TIME,\n"
+ + "`DateTimeField` DATETIME,\n"
+ + "`GeographyField` GEOGRAPHY,\n"
+ + "`RecordField` STRUCT,\n"
+ + "`JsonField` JSON,\n"
+ + ");";
+ public static final String insertQuery1 =
+ "INSERT INTO "
+ + " `%s.%s.%s` "
+ + " (\n"
+ + "StringField, BytesField,IntegerField,FloatField,NumericField,BigNumericField,BooleanField,\n"
+ + "TimestampField,DateField,TimeField,DateTimeField,GeographyField,RecordField,JsonField )\n"
+ + "VALUES('string1',CAST ('string1' AS BYTES),111,1.1, CAST('11.1E11' AS NUMERIC), \n"
+ + "CAST('1.1E37' AS BIGNUMERIC), TRUE,CAST('2001-05-1 8:05:01' AS TIMESTAMP), \n"
+ + "CAST('2001-05-1' AS DATE),CAST('5:1:11.041' AS TIME), CAST('2001-05-1 11:31:45' AS DATETIME), \n"
+ + "CAST(ST_GEOGFROMTEXT('POINT(1.500989010415034 -1.11471081311336843)') AS GEOGRAPHY), \n"
+ + "CAST(('name1', 1) AS STRUCT), \n"
+ + " JSON \"\"\"{\n"
+ + " \"name\": \"Alice1\",\n"
+ + " \"items\": [\n"
+ + " {\"product\": \"book1\", \"price\": 1},\n"
+ + " {\"product\": \"food1\", \"price\": 1}\n"
+ + " ]\n"
+ + " }\"\"\"\n"
+ + ");";
+ public static final String insertQuery2 =
+ "INSERT INTO "
+ + " `%s.%s.%s` "
+ + " (\n"
+ + " StringField, BytesField,IntegerField,FloatField,NumericField,BigNumericField,BooleanField,\n"
+ + " TimestampField,DateField,TimeField,DateTimeField,GeographyField,RecordField,JsonField )\n"
+ + " VALUES('string2',CAST ('string2' AS BYTES),222,2.2, CAST('22.2E22' AS NUMERIC),\n"
+ + " CAST('2.2E37' AS BIGNUMERIC), TRUE,CAST('2002-05-2 8:05:02' AS TIMESTAMP),\n"
+ + " CAST('2002-05-2' AS DATE),CAST('5:2:22.042' AS TIME), CAST('2002-05-2 22:32:45' AS DATETIME),\n"
+ + " CAST(ST_GEOGFROMTEXT('POINT(2.500989020425034 -2.22472082322336843)') AS GEOGRAPHY),\n"
+ + " CAST(('name2', 2) AS STRUCT),\n"
+ + " JSON \"\"\"{\n"
+ + " \"name\": \"Alice2\",\n"
+ + " \"items\": [\n"
+ + " {\"product\": \"book2\", \"price\": 2},\n"
+ + " {\"product\": \"food2\", \"price\": 2}\n"
+ + " ]\n"
+ + " }\"\"\"\n"
+ + " );";
+
+ public static final String createProcedure =
+ "CREATE OR REPLACE PROCEDURE `%s.%s`.create_customer() \n"
+ + "\tBEGIN\n"
+ + "\t\tDECLARE id STRING;\n"
+ + "\t\tSET id = GENERATE_UUID();\n"
+ + "\t\tINSERT INTO `%s.%s.%s` (StringField) VALUES(id);\n"
+ + "\t\tSELECT FORMAT(\"Created customer.\");\n"
+ + "\tEND";
+
+ public static void setUpProcedure(String dataset, String table) throws InterruptedException {
+ {
+ BigQuery bigQuery = BigQueryOptions.getDefaultInstance().getService();
+ bigQuery.query(
+ QueryJobConfiguration.of(
+ String.format(
+ createProcedure, DEFAULT_CATALOG, dataset, DEFAULT_CATALOG, dataset, table)));
+ }
+ }
+
+ public static void setUpDataset(String dataset) throws InterruptedException {
+ BigQuery bigQuery = BigQueryOptions.getDefaultInstance().getService();
+ bigQuery.query(
+ QueryJobConfiguration.of(String.format(createDatasetQuery, DEFAULT_CATALOG, dataset)));
+ }
+
+ public static void setUpTable(String dataset, String table) throws InterruptedException {
+ BigQuery bigQuery = BigQueryOptions.getDefaultInstance().getService();
+ bigQuery.query(
+ QueryJobConfiguration.of(String.format(createTableQuery, DEFAULT_CATALOG, dataset, table)));
+ bigQuery.query(
+ QueryJobConfiguration.of(String.format(insertQuery1, DEFAULT_CATALOG, dataset, table)));
+ bigQuery.query(
+ QueryJobConfiguration.of(String.format(insertQuery2, DEFAULT_CATALOG, dataset, table)));
+ }
+
+ public static void cleanUp(String dataset) throws InterruptedException {
+ BigQuery bigQuery = BigQueryOptions.getDefaultInstance().getService();
+ bigQuery.query(QueryJobConfiguration.of(String.format(dropSchema, DEFAULT_CATALOG, dataset)));
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/authentication/ITJdbcAuthenticationTest.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/authentication/ITJdbcAuthenticationTest.java
new file mode 100644
index 000000000000..f0cedff66d52
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/authentication/ITJdbcAuthenticationTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2026 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.javatests.authentication;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.File;
+import com.google.cloud.ServiceOptions;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import java.sql.PreparedStatement;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class ITJdbcAuthenticationTest {
+ Connection connection = null;
+
+ private static String connectionUriBase = "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;OAuthType=0;";
+
+ @Before
+ public void beforeTest(){
+ connection = null;
+ }
+
+ @After
+ public void afterTest() throws Exception {
+ if (connection != null){
+ connection.close();
+ }
+ connection = null;
+ }
+
+ 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 Exception {
+ final String secret = requireEnvVar("SA_SECRET");
+ JsonObject authJson = JsonParser.parseString(secret).getAsJsonObject();
+
+ assertTrue(authJson.has("client_email"));
+ assertTrue(authJson.has("private_key"));
+ assertTrue(authJson.has("project_id"));
+ return authJson;
+ }
+
+ private void validateConnectionUri(String connectionUri) throws Exception {
+ connection =
+ DriverManager.getConnection(connectionUri);
+ assertFalse(connection.isClosed());
+ PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1");
+ assertNotNull(preparedStatement);
+ preparedStatement.execute();
+ preparedStatement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testConnectOAuthPvtKeyPath() throws Exception {
+ final JsonObject authJson = getAuthJson();
+ File tempFile = File.createTempFile("auth", ".json");
+ tempFile.deleteOnExit();
+ Files.write(tempFile.toPath(), authJson.toString().getBytes());
+
+ final String connectionUri = connectionUriBase +
+ ";ProjectId=" + authJson.get("project_id").getAsString() +
+ ";OAuthServiceAcctEmail=" +
+ ";OAuthPvtKeyPath=" + tempFile.toPath();
+ validateConnectionUri(connectionUri);
+ }
+
+ @Test
+ public void testConnectOAuthPvtKeyAsPath() throws Exception {
+ final JsonObject authJson = getAuthJson();
+ File tempFile = File.createTempFile("auth", ".json");
+ tempFile.deleteOnExit();
+ Files.write(tempFile.toPath(), authJson.toString().getBytes());
+
+ final String connectionUri = connectionUriBase +
+ ";ProjectId=" + authJson.get("project_id").getAsString() +
+ ";OAuthServiceAcctEmail=" +
+ ";OAuthPvtKey=" + tempFile.toPath();
+ validateConnectionUri(connectionUri);
+ }
+
+ @Test
+ public void testConnectOAuthPvtKeyAsJson() throws Exception {
+ final JsonObject authJson = getAuthJson();
+ final String connectionUri = connectionUriBase +
+ ";ProjectId=" + authJson.get("project_id").getAsString() +
+ ";OAuthServiceAcctEmail=" +
+ ";OAuthPvtKey=" + authJson.toString();
+ validateConnectionUri(connectionUri);
+ }
+
+ @org.junit.Ignore("Disabled until p12 file can be supplied")
+ @Test
+ public void testConnectOAuthPvtKeyP12() throws Exception {
+ final JsonObject authJson = getAuthJson();
+ final String p12_file = requireEnvVar("SA_SECRET_P12");
+
+ final String connectionUri = connectionUriBase +
+ ";ProjectId=" + authJson.get("project_id").getAsString() +
+ ";OAuthServiceAcctEmail=" + authJson.get("client_email").getAsString() +
+ ";OAuthPvtKeyPath=" + p12_file;
+ validateConnectionUri(connectionUri);
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/connection/ITJdbcConnectionTest.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/connection/ITJdbcConnectionTest.java
new file mode 100644
index 000000000000..b4cd489bd8a2
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/connection/ITJdbcConnectionTest.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright 2026 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.javatests.connection;
+
+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.bigquery.jdbc.javatests.JdbcConnectionHelper;
+import com.google.cloud.ServiceOptions;
+import java.sql.Array;
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.Random;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ITJdbcConnectionTest {
+
+ private static final String DATASET = "EXT_JDBC_CONNECTION_TEST_DATASET";
+ private static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ static Random random = new Random();
+ static int randomNumber = random.nextInt(999);
+ private static final String TABLE_NAME = "EXT_JDBC_CONNECTION_TEST_TABLE" + randomNumber;
+
+ private static String connectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=%s;OAuthType=3;Timeout=3600;";
+
+ @BeforeClass
+ public static void beforeClass() throws InterruptedException {
+
+ JdbcConnectionHelper.setUpDataset(DATASET);
+ JdbcConnectionHelper.setUpTable(DATASET, TABLE_NAME);
+ JdbcConnectionHelper.setUpProcedure(DATASET, TABLE_NAME);
+ }
+
+ @AfterClass
+ public static void afterClass() throws InterruptedException {
+ JdbcConnectionHelper.cleanUp(DATASET);
+ }
+
+ @Test
+ public void testGetMetaData() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertFalse(connection.isClosed());
+ DatabaseMetaData metaData = connection.getMetaData();
+ assertNotNull(metaData);
+ assertEquals("Google BigQuery", metaData.getDatabaseProductName());
+ assertEquals(4, metaData.getJDBCMajorVersion());
+ assertEquals(2, metaData.getJDBCMinorVersion());
+ assertEquals("Google BigQuery", metaData.getDatabaseProductName());
+ assertEquals("SimbaJDBCDriverforGoogleBigQuery", metaData.getDriverName());
+ }
+
+ @Test
+ public void testCreateStatement() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertFalse(connection.isClosed());
+ Statement statement = connection.createStatement();
+ assertNotNull(statement);
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testPrepareStatement1() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1");
+ assertNotNull(preparedStatement);
+ preparedStatement.execute();
+ preparedStatement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testPrepareStatement2() throws SQLException {
+
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ PreparedStatement preparedStatement =
+ connection.prepareStatement("SELECT 1", Statement.NO_GENERATED_KEYS);
+ assertNotNull(preparedStatement);
+ preparedStatement.execute();
+ preparedStatement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testPrepareStatement3() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(
+ SQLFeatureNotSupportedException.class,
+ () -> connection.prepareStatement("SELECT 1", new int[] {1, 2, 3}));
+ }
+
+ @Test
+ public void testPrepareStatement4() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ PreparedStatement preparedStatement =
+ connection.prepareStatement(
+ "SELECT 1", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ assertNotNull(preparedStatement);
+ preparedStatement.execute();
+ preparedStatement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testPrepareStatement5() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(
+ SQLFeatureNotSupportedException.class,
+ () ->
+ connection.prepareStatement(
+ "SELECT 1",
+ ResultSet.TYPE_FORWARD_ONLY,
+ ResultSet.CONCUR_READ_ONLY,
+ ResultSet.HOLD_CURSORS_OVER_COMMIT));
+ }
+
+ @Test
+ public void testPrepareStatement6() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(
+ SQLFeatureNotSupportedException.class,
+ () -> connection.prepareStatement("SELECT 1", new String[] {"a", "b"}));
+ }
+
+ @Test
+ public void testPrepareCall1() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ try {
+ CallableStatement callableStatement =
+ connection.prepareCall(
+ String.format("call `%s.%s`.create_customer()", DEFAULT_CATALOG, DATASET));
+
+ assertNotNull(callableStatement);
+ callableStatement.execute();
+ callableStatement.close();
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testPrepareCall2() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ try {
+
+ CallableStatement callableStatement1 =
+ connection.prepareCall(
+ String.format("call `%s.%s`.create_customer()", DEFAULT_CATALOG, DATASET),
+ ResultSet.TYPE_FORWARD_ONLY,
+ ResultSet.CONCUR_READ_ONLY);
+ assertNotNull(callableStatement1);
+ callableStatement1.execute();
+ assertNull(callableStatement1.getResultSet());
+ callableStatement1.close();
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testPrepareCall3() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(
+ SQLFeatureNotSupportedException.class,
+ () ->
+ connection.prepareCall(
+ String.format("call %s.%s.create_customer()", DEFAULT_CATALOG, DATASET),
+ ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_READ_ONLY,
+ ResultSet.HOLD_CURSORS_OVER_COMMIT));
+ }
+
+ @Test
+ public void testSetAutoCommit() throws SQLException {
+ Properties properties = new Properties();
+ properties.setProperty("EnableSession", "1");
+
+ Connection connection1 =
+ DriverManager.getConnection(JdbcConnectionHelper.connectionUrl, properties);
+ connection1.setAutoCommit(false);
+ assertFalse(connection1.getAutoCommit());
+ connection1.setAutoCommit(true);
+ assertTrue(connection1.getAutoCommit());
+ connection1.close();
+ }
+
+ @Test
+ public void testCommitAndRollback() throws SQLException {
+
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+ Properties properties = new Properties();
+ properties.setProperty("EnableSession", "1");
+
+ Connection connection1 =
+ DriverManager.getConnection(JdbcConnectionHelper.connectionUrl, properties);
+ connection1.setAutoCommit(false);
+ Statement statement1 = connection1.createStatement();
+ statement.execute(
+ String.format(
+ "CREATE TABLE IF NOT EXISTS %s.%s.autocommit_test_table (id INT)",
+ DEFAULT_CATALOG, DATASET));
+ connection1.commit();
+
+ statement1.executeUpdate(
+ String.format(
+ "INSERT INTO %s.%s.autocommit_test_table (id) VALUES (1)", DEFAULT_CATALOG, DATASET));
+ connection1.rollback();
+
+ ResultSet resultSet =
+ statement1.executeQuery(
+ String.format(
+ "SELECT COUNT(*) FROM %s.%s.autocommit_test_table", DEFAULT_CATALOG, DATASET));
+ resultSet.next();
+ assertEquals(0, resultSet.getInt(1)); // Table should be empty due to rollback
+
+ connection1.setAutoCommit(true);
+ statement1.executeUpdate(
+ String.format(
+ "DROP TABLE IF EXISTS %s.%s.autocommit_test_table", DEFAULT_CATALOG, DATASET));
+ statement1.close();
+ connection1.close();
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testSetCatalog() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ try {
+ String catalog = connection.getCatalog();
+ if (catalog != null) {
+ connection.setCatalog(catalog);
+ assertEquals(catalog, connection.getCatalog());
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testSetSchema() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ try {
+ String schema = connection.getSchema();
+ if (schema != null) {
+ connection.setSchema(schema);
+ assertEquals(schema, connection.getSchema());
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testSetClientInfo() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData databaseMetaData = connection.getMetaData();
+ ResultSet resultSet = databaseMetaData.getClientInfoProperties();
+ while (resultSet.next()) {
+ assertNotNull(resultSet.getString("NAME"));
+ assertNull(resultSet.getString("DEFAULT_VALUE"));
+ }
+ connection.setClientInfo("APPLICATIONNAME", "MyTestApp");
+ assertEquals("MyTestApp", connection.getClientInfo("APPLICATIONNAME"));
+ connection.setClientInfo("CLIENTUSER", "test");
+ assertEquals("test", connection.getClientInfo("CLIENTUSER"));
+
+ // Test setClientInfo with Properties
+ java.util.Properties properties = new java.util.Properties();
+ properties.setProperty("CLIENTHOSTNAME", "testHost");
+ connection.setClientInfo(properties);
+ assertEquals("testHost", connection.getClientInfo("CLIENTHOSTNAME"));
+ connection.setClientInfo("CLIENTHOSTNAME", "testHost23");
+ assertEquals("testHost23", connection.getClientInfo("CLIENTHOSTNAME"));
+ connection.close();
+ }
+
+ @Test
+ public void testSetNetworkTimeout() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(
+ SQLFeatureNotSupportedException.class,
+ () -> connection.setNetworkTimeout(null, 1000)); // 1 second timeout
+ }
+
+ @Test
+ public void testCreateClob() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(SQLFeatureNotSupportedException.class, () -> connection.createClob());
+ }
+
+ @Test
+ public void testCreateBlob() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(SQLFeatureNotSupportedException.class, () -> connection.createBlob());
+ }
+
+ @Test
+ public void testCreateNClob() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(SQLFeatureNotSupportedException.class, () -> connection.createNClob());
+ }
+
+ @Test
+ public void testCreateSQLXML() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(SQLFeatureNotSupportedException.class, () -> connection.createSQLXML());
+ }
+
+ @Test
+ public void testCreateArray() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ try {
+ Array array = connection.createArrayOf("INTEGER", new Object[] {1, 2, 3});
+ assertNotNull(array);
+
+ array.free(); // Remember to free resources
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testCreateStruct() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertThrows(
+ SQLFeatureNotSupportedException.class,
+ () -> connection.createStruct("ADDRESS", new Object[] {"123 Main St", "Anytown"}));
+ }
+
+ @Test
+ public void testNativeSQL() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ String sql = "SELECT 1";
+ String nativeSql = connection.nativeSQL(sql);
+ assertEquals(sql, nativeSql);
+ assertNotNull(nativeSql);
+ connection.close();
+ }
+
+ @Test
+ public void testSetSavepoint() throws SQLException {
+ Properties properties = new Properties();
+ properties.setProperty("EnableSession", "1");
+
+ Connection connection1 =
+ DriverManager.getConnection(JdbcConnectionHelper.connectionUrl, properties);
+ connection1.setAutoCommit(false);
+ Statement statement = connection1.createStatement();
+ statement.execute("SELECT 1; SELECT 2; SELECT 3;");
+ assertThrows(SQLFeatureNotSupportedException.class, () -> connection1.setSavepoint());
+ assertThrows(
+ SQLFeatureNotSupportedException.class, () -> connection1.setSavepoint("mySavepoint"));
+ assertThrows(
+ SQLFeatureNotSupportedException.class,
+ () -> connection1.releaseSavepoint(connection1.setSavepoint()));
+ connection1.setAutoCommit(true);
+ connection1.close();
+ }
+
+ // TODO:rewrite test
+ @Test
+ public void testAbort() throws SQLException, InterruptedException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ // This test is tricky to automate fully, as it involves asynchronous behavior
+ // We'll just test that we can call it without throwing an exception immediately
+
+ // Create an ExecutorService to simulate asynchronous operation
+ java.util.concurrent.ExecutorService executor =
+ java.util.concurrent.Executors.newSingleThreadExecutor();
+ executor.submit(
+ () -> {
+ try {
+ // This will likely throw an exception as the connection is already open
+ // But we mainly want to test that calling abort doesn't cause a direct error
+ assertFalse(connection.isClosed());
+ assertTrue(connection.isClosed());
+ connection.abort(java.util.concurrent.Executors.newSingleThreadExecutor());
+ assertTrue(connection.isClosed());
+ } catch (SQLException e) {
+ e.printStackTrace();
+ // It's expected that calling abort might cause an exception
+ // depending on the state of the connection and driver.
+ }
+ });
+
+ // Give the thread a short time to execute
+ Thread.sleep(100);
+
+ // Shutdown the executor
+ executor.shutdown();
+ executor.awaitTermination(100, java.util.concurrent.TimeUnit.MILLISECONDS);
+ connection.close();
+ }
+
+ @Test
+ public void testIsWrapperFor() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertTrue(connection.isWrapperFor(Connection.class));
+ connection.close();
+ }
+
+ @Test
+ public void testIsValid() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ assertTrue(connection.isValid(0)); // 0 seconds timeout
+ connection.close();
+ }
+
+ @Test
+ public void testSimbaConnectionImpl() throws SQLException {
+ String OAUTH_TYPE = "3";
+ String CONNECTION_URL =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId="
+ + DEFAULT_CATALOG
+ + ";OAuthType="
+ + OAUTH_TYPE
+ + ";EnableSession=1;";
+
+ Connection connection = DriverManager.getConnection(CONNECTION_URL);
+ assertFalse(connection.isClosed());
+ Statement statement =
+ connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ assertEquals(ResultSet.FETCH_FORWARD, statement.getFetchDirection());
+ assertFalse(connection.isReadOnly());
+ connection.setAutoCommit(false);
+ connection.close();
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/connection/ITJdbcDriverTest.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/connection/ITJdbcDriverTest.java
new file mode 100644
index 000000000000..a281773e4059
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/connection/ITJdbcDriverTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2026 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.javatests.connection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.bigquery.jdbc.javatests.JdbcConnectionHelper;
+import com.google.cloud.ServiceOptions;
+import com.google.cloud.bigquery.BigQuery;
+import com.google.cloud.bigquery.BigQueryOptions;
+import com.google.cloud.bigquery.Dataset;
+import com.google.cloud.bigquery.DatasetId;
+import java.sql.Driver;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.Random;
+import org.junit.Test;
+
+public class ITJdbcDriverTest {
+
+ private static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ static Random random = new Random();
+ static int randomNumber = random.nextInt(999);
+ String createDataset = "CREATE SCHEMA `%s.%s` OPTIONS(default_table_expiration_days = 5);";
+ String createQuery =
+ "CREATE OR REPLACE TABLE `%s.%s.%s` "
+ + " (\n"
+ + "`StringField` STRING,\n"
+ + "`BytesField` BYTES,\n"
+ + "`IntegerField` INTEGER"
+ + ");";
+ String insertQuery1 =
+ "INSERT INTO `%s.%s.%s` "
+ + " (StringField, BytesField,IntegerField) "
+ + "VALUES('string1', CAST('string1' AS BYTES),111);";
+
+ String selectQuery = "SELECT * FROM `%s.%s.%s` ;";
+
+ @Test
+ public void testJDBCDriver() throws SQLException, InterruptedException {
+ String OAUTH_TYPE = "3";
+ String CONNECTION_URL =
+ "jdbc:bigquery://https://bigquery.googleapis.com/bigquery/v2/:443;ProjectId="
+ + DEFAULT_CATALOG
+ + ";OAuthType="
+ + OAUTH_TYPE
+ + ";LOCATION=US;";
+
+ String dataset = "EXT_JDBC_DRIVER_TEST_DATASET" + random.nextInt(999);
+ String tableName = "EXT_JDBC_DRIVER_TEST_TABLE" + randomNumber;
+
+ Driver driver = DriverManager.getDriver(CONNECTION_URL);
+
+ Connection con = driver.connect(CONNECTION_URL, new Properties());
+ assertTrue(driver.acceptsURL(CONNECTION_URL));
+ assertFalse(driver.jdbcCompliant());
+ assertEquals(1, driver.getMajorVersion());
+ assertEquals(6, driver.getMinorVersion());
+
+ Statement statement = con.createStatement();
+ statement.execute(String.format(createDataset, DEFAULT_CATALOG, dataset));
+ statement.execute(String.format(createQuery, DEFAULT_CATALOG, dataset, tableName));
+ boolean insertResult =
+ statement.execute(String.format(insertQuery1, DEFAULT_CATALOG, dataset, tableName));
+ assertFalse(insertResult);
+ statement.execute(String.format(selectQuery, DEFAULT_CATALOG, dataset, tableName));
+ ResultSet res = statement.getResultSet();
+ assertTrue(res.next());
+ JdbcConnectionHelper.cleanUp(dataset);
+ con.close();
+ }
+
+ @Test
+ public void testDriverLocation() throws SQLException, InterruptedException {
+
+ String datasetUS = "EXT_JDBC_DRIVER_US_TEST_DATASET" + random.nextInt(999);
+ String tableNameUS = "EXT_JDBC_DRIVER_US_TEST_TABLE" + randomNumber;
+ String OAUTH_TYPE = "3";
+ String CONNECTION_URL =
+ "jdbc:bigquery://https://bigquery.googleapis.com/bigquery/v2/:443;ProjectId=%s;OAuthType=%s;LOCATION=%s;";
+
+ // US Connection
+ Connection connectionUS =
+ DriverManager.getConnection(
+ String.format(CONNECTION_URL, DEFAULT_CATALOG, OAUTH_TYPE, "us-east5"));
+ Statement statementUS = connectionUS.createStatement();
+ statementUS.execute(String.format(createDataset, DEFAULT_CATALOG, datasetUS));
+ statementUS.execute(String.format(createQuery, DEFAULT_CATALOG, datasetUS, tableNameUS));
+ boolean insertResult =
+ statementUS.execute(String.format(insertQuery1, DEFAULT_CATALOG, datasetUS, tableNameUS));
+ assertFalse(insertResult);
+ statementUS.execute(String.format(selectQuery, DEFAULT_CATALOG, datasetUS, tableNameUS));
+ ResultSet res = statementUS.getResultSet();
+ assertTrue(res.next());
+
+ BigQuery bigQuery = BigQueryOptions.getDefaultInstance().getService();
+ Dataset retrievedDataset = bigQuery.getDataset(DatasetId.of(DEFAULT_CATALOG, datasetUS));
+ assertEquals("us-east5", retrievedDataset.getLocation());
+ JdbcConnectionHelper.cleanUp(datasetUS);
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/metadata/ITJdbcDatabaseMetadataTest.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/metadata/ITJdbcDatabaseMetadataTest.java
new file mode 100644
index 000000000000..f169b2e6abb6
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/metadata/ITJdbcDatabaseMetadataTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2026 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.javatests.metadata;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.bigquery.jdbc.javatests.BaseDatabaseMetadata;
+import com.google.cloud.bigquery.jdbc.javatests.JdbcConnectionHelper;
+import com.google.cloud.ServiceOptions;
+import com.google.cloud.bigquery.BigQuery;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.regex.Pattern;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ITJdbcDatabaseMetadataTest extends BaseDatabaseMetadata {
+
+ private static final Pattern VERSION_PATTERN =
+ Pattern.compile("^(\\d+)\\.(\\d+)(?:\\.\\d+)+\\s*.*");
+ private static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ private static final String DATASET = "EXT_JDBC_DBMETADATA_TEST_DATASET";
+ static Random random = new Random();
+ static int randomNumber = random.nextInt(999);
+ private static final String TABLE_NAME = "EXT_JDBC_DBMETADATA_TEST_TABLE" + randomNumber;
+ private static String connectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=%s;OAuthType=3;Timeout=3600;";
+ private static BigQuery bigQuery;
+
+ @BeforeClass
+ public static void beforeClass() throws InterruptedException {
+ // Set up Dataset
+ JdbcConnectionHelper.setUpDataset(DATASET);
+ JdbcConnectionHelper.setUpTable(DATASET, TABLE_NAME);
+ }
+
+ @AfterClass
+ public static void afterClass() throws InterruptedException {
+ JdbcConnectionHelper.cleanUp(DATASET);
+ }
+
+ @Test
+ public void testGetCatalogs() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ verifyCatalogHelper(metaData, ".", "Project", 128, DEFAULT_CATALOG, true, true);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ // This test is throwing the following error: [Simba][BigQueryJDBCDriver](100068) Error fetching
+ // metadata
+ @Test
+ public void testGetSchemas() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ verifySchemaHelper(connection, metaData, "Dataset", 1024, true, true);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testGetTableTypes() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+ // this is either 3 or 4 in different projects
+
+ verifyTableTypes(metaData, Arrays.asList("EXTERNAL", "TABLE", "VIEW"));
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testGetTables() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ verifyGetTables(connection, metaData, DATASET);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ // this currently returns empty.
+ // TODO: Make it return non-empty
+ @Test
+ public void testGetTablePrivileges() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ verifyGetTablePrivileges(connection, metaData, DATASET);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testGetClientInfoProperties() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ ResultSet resultSet = metaData.getClientInfoProperties();
+ // Resultset is not empty
+ assertTrue(resultSet.next());
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testGetColumns() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ verifyGetColumns(connection, metaData, DATASET);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testDriverMetadataInfo() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ verifyDriverMetadata(
+ metaData, 4, 2, "Google BigQuery", "SimbaJDBCDriverforGoogleBigQuery", VERSION_PATTERN);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testProcedure() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+
+ verifyTestProcedure(metaData, "procedure");
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testAllBooleanMethods() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+ // This method toggles between true and false in different environments
+ assertFalse(metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_NONE)); // f
+ assertFalse(
+ metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED));
+ assertFalse(
+ metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED)); // f
+
+ assertFalse(
+ metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ)); // f
+ assertTrue(metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE));
+ assertEquals(Connection.TRANSACTION_SERIALIZABLE, metaData.getDefaultTransactionIsolation());
+ assertTrue(metaData.supportsTransactions());
+
+ verifyAllBooleanMethods(metaData);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testAllIntMethods() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+ verifyIntMethods(metaData);
+ assertEquals(2, metaData.getResultSetHoldability()); // 2
+ assertEquals(2, metaData.getDatabaseMajorVersion()); // 2
+ assertEquals(0, metaData.getDatabaseMinorVersion()); // 0
+ assertEquals(2, metaData.getSQLStateType());
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testAllStringMethods() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+ verifyAllStringMethods(metaData, "procedure");
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testGetPrimaryKeys() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ DatabaseMetaData metaData = connection.getMetaData();
+ try {
+ verifyGetPrimaryKeys(connection, metaData, DATASET);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/metadata/ITJdbcResultSetMetadataTest.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/metadata/ITJdbcResultSetMetadataTest.java
new file mode 100644
index 000000000000..7f4093592c83
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/metadata/ITJdbcResultSetMetadataTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2026 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.javatests.metadata;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.cloud.bigquery.jdbc.javatests.JdbcConnectionHelper;
+import com.google.cloud.ServiceOptions;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Random;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ITJdbcResultSetMetadataTest {
+
+ private static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ static Random random = new Random();
+ static int randomNumber = random.nextInt(999);
+ private static final String TABLE_NAME = "EXT_JDBC_RSMETADATA_TEST_TABLE" + randomNumber;
+ private static final String DATASET = "EXT_JDBC_RSMETADATA_TEST_DATASET";
+ private static ResultSetMetaData metaData;
+
+ @BeforeClass
+ public static void beforeClass() throws InterruptedException {
+ JdbcConnectionHelper.setUpDataset(DATASET);
+ JdbcConnectionHelper.setUpTable(DATASET, TABLE_NAME);
+ }
+
+ @AfterClass
+ public static void afterClass() throws InterruptedException {
+ JdbcConnectionHelper.cleanUp(DATASET);
+ }
+
+ @Test
+ public void testResultSetMetadata() throws SQLException {
+ String selectData = "SELECT * FROM " + DATASET + "." + TABLE_NAME + ";";
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(JdbcConnectionHelper.connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery(selectData);
+ metaData = resultSet.getMetaData();
+ try {
+ assertEquals(DEFAULT_CATALOG, metaData.getCatalogName(1));
+
+ assertEquals(14, metaData.getColumnCount());
+
+ assertEquals("StringField", metaData.getColumnName(1));
+ assertEquals("StringField", metaData.getColumnLabel(1));
+ assertEquals("java.lang.String", metaData.getColumnClassName(1));
+ assertEquals(65535, metaData.getColumnDisplaySize(1));
+ assertEquals("STRING", metaData.getColumnTypeName(1));
+ assertEquals(12, metaData.getColumnType(1));
+
+ assertEquals(26, metaData.getColumnDisplaySize(8));
+ assertEquals("TIMESTAMP", metaData.getColumnTypeName(8));
+ assertEquals(93, metaData.getColumnType(8));
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ connection.close();
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/statement/ITBigQueryJDBCTest.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/statement/ITBigQueryJDBCTest.java
new file mode 100644
index 000000000000..9dc15c1028d0
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/statement/ITBigQueryJDBCTest.java
@@ -0,0 +1,1655 @@
+/*
+ * Copyright 2026 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.javatests.statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.bigquery.jdbc.javatests.JdbcConnectionHelper;
+import com.google.cloud.ServiceOptions;
+import java.nio.charset.StandardCharsets;
+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.util.Properties;
+import java.util.Random;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ITBigQueryJDBCTest {
+
+ static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ private static String connectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=%s;OAuthType=3;Timeout=3600;";
+ private static String noReadAPIConnectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=%s;OAuthType=3;Timeout=3600;"
+ + "EnableHighThroughputAPI=0;HighThroughputActivationRatio=30;HighThroughputMinTableSize=200000000;";
+ static final String session_enabled_connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID="
+ + DEFAULT_CATALOG
+ + ";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 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 DATASET = "JDBC_BIGQUERY_TEST_DATASET";
+ static Random random = new Random();
+ static int randomNumber = random.nextInt(999);
+ private static final String TABLE_NAME = "EXT_JDBC_BIGQUERY_TEST_TABLE" + randomNumber;
+
+ @BeforeClass
+ public static void beforeClass() throws InterruptedException {
+ JdbcConnectionHelper.setUpDataset(DATASET);
+ JdbcConnectionHelper.setUpTable(DATASET, TABLE_NAME);
+ JdbcConnectionHelper.setUpProcedure(DATASET, TABLE_NAME);
+ }
+
+ @AfterClass
+ public static void afterClass() throws InterruptedException {
+ JdbcConnectionHelper.cleanUp(DATASET);
+ }
+
+ @Test
+ public void testValidApplicationDefaultCredentialsAuthentication() throws SQLException {
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;ProjectId="
+ + DEFAULT_CATALOG
+ + ";";
+
+ Connection connection = DriverManager.getConnection(connection_uri);
+ assertNotNull(connection);
+ assertFalse(connection.isClosed());
+ connection.close();
+ }
+
+ @Test
+ public void testFastQueryPathSmall() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ String query =
+ "SELECT DISTINCT repository_name FROM `bigquery-public-data.samples.github_timeline` LIMIT"
+ + " 850";
+ ResultSet jsonResultSet = bigQueryStatement.executeQuery(query);
+ assertEquals(850, resultSetRowCount(jsonResultSet));
+ connection.close();
+ }
+
+ @Test
+ public void testFastQueryPathEmpty() throws SQLException {
+ String query =
+ "SELECT DISTINCT repository_name FROM `bigquery-public-data.samples.github_timeline` LIMIT"
+ + " 0";
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ ResultSet jsonResultSet = bigQueryStatement.executeQuery(query);
+ assertEquals(0, resultSetRowCount(jsonResultSet));
+ connection.close();
+ }
+
+ @Test
+ public void testStatelessQueryPathSmall() throws SQLException {
+ Properties jobCreationMode = new Properties();
+ jobCreationMode.setProperty("JobCreationMode", "2");
+ Connection connectionUseStateless =
+ DriverManager.getConnection(JdbcConnectionHelper.connectionUrl, jobCreationMode);
+
+ Statement statement = connectionUseStateless.createStatement();
+
+ String query =
+ "SELECT DISTINCT repository_name FROM `bigquery-public-data.samples.github_timeline` LIMIT"
+ + " 850";
+ ResultSet jsonResultSet = statement.executeQuery(query);
+ assertEquals(850, resultSetRowCount(jsonResultSet));
+
+ String queryEmpty =
+ "SELECT DISTINCT repository_name FROM `bigquery-public-data.samples.github_timeline` LIMIT"
+ + " 0";
+ ResultSet jsonResultSetEmpty = statement.executeQuery(queryEmpty);
+ assertEquals(0, resultSetRowCount(jsonResultSetEmpty));
+ connectionUseStateless.close();
+ }
+
+ @Test
+ public void testFastQueryPathMedium() throws SQLException {
+ String query =
+ "SELECT repository_name FROM `bigquery-public-data.samples.github_timeline` LIMIT 9000";
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ ResultSet jsonResultSet = bigQueryStatement.executeQuery(query);
+ assertEquals(9000, resultSetRowCount(jsonResultSet));
+ connection.close();
+ }
+
+ @Test
+ public void testFastQueryPathLarge() throws SQLException {
+ String query =
+ "SELECT repository_name FROM `bigquery-public-data.samples.github_timeline` LIMIT 18000";
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ ResultSet jsonResultSet = bigQueryStatement.executeQuery(query);
+ assertEquals(18000, resultSetRowCount(jsonResultSet));
+ connection.close();
+ }
+
+ @Test
+ // reads using ReadAPI and makes sure that they are in order, which implies threads worked
+ // correctly
+ public void testIterateOrderArrowMultiThread() throws SQLException {
+ int expectedCnt = 200000;
+ String longQuery = String.format(BASE_QUERY, expectedCnt);
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ ResultSet rs = bigQueryStatement.executeQuery(longQuery);
+ 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
+ connection.close();
+ }
+
+ @Test
+ public void testReadAPIPathLarge() throws SQLException {
+ Properties withReadApi = new Properties();
+ withReadApi.setProperty("EnableHighThroughputAPI", "1");
+ withReadApi.setProperty("HighThroughputActivationRatio", "2");
+ withReadApi.setProperty("HighThroughputMinTableSize", "1000");
+ withReadApi.setProperty("MaxResults", "300");
+
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG), withReadApi);
+ Statement statement = connection.createStatement();
+ int expectedCnt = 5000;
+ String longQuery = String.format(BASE_QUERY, expectedCnt);
+ ResultSet arrowResultSet = statement.executeQuery(longQuery);
+ assertEquals(expectedCnt, resultSetRowCount(arrowResultSet));
+ arrowResultSet.close();
+ connection.close();
+ }
+
+ @Test
+ public void testReadAPIPathLargeWithThresholdParameters() throws SQLException {
+ String connectionUri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID="
+ + DEFAULT_CATALOG
+ + ";OAUTHTYPE=3;MaxResults=300;HighThroughputActivationRatio=2;"
+ + "HighThroughputMinTableSize=100;EnableHighThroughputAPI=1";
+ Connection connection = DriverManager.getConnection(connectionUri);
+ Statement statement = connection.createStatement();
+ int expectedCnt = 1000;
+ String longQuery = String.format(BASE_QUERY, expectedCnt);
+ ResultSet arrowResultSet = statement.executeQuery(longQuery);
+ assertEquals(expectedCnt, resultSetRowCount(arrowResultSet));
+ arrowResultSet.close();
+ connection.close();
+ }
+
+ @Test
+ public void testReadAPIPathLargeWithThresholdNotMet() throws SQLException {
+ String connectionUri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID="
+ + DEFAULT_CATALOG
+ + ";OAUTHTYPE=3;HighThroughputActivationRatio=4;"
+ + "HighThroughputMinTableSize=100;EnableHighThroughputAPI=1";
+ Connection connection = DriverManager.getConnection(connectionUri);
+ Statement statement = connection.createStatement();
+ int expectedCnt = 5000;
+ String longQuery = String.format(BASE_QUERY, expectedCnt);
+ ResultSet arrowResultSet = statement.executeQuery(longQuery);
+ assertEquals(expectedCnt, resultSetRowCount(arrowResultSet));
+ arrowResultSet.close();
+ connection.close();
+ }
+
+ @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";
+
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ ResultSet resultSet = bigQueryStatement.executeQuery(query);
+ int rowCount = 0;
+ while (resultSet.next()) {
+ assertTrue(resultSet.getString(1).startsWith("X"));
+ rowCount++;
+ }
+ assertEquals(10, rowCount);
+ connection.close();
+ }
+
+ @Test
+ public void testInvalidQuery() throws SQLException {
+ String query = "SELECT *";
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+
+ try {
+ bigQueryStatement.executeQuery(query);
+ Assert.fail();
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("SELECT * must have a FROM clause"));
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testLocation() throws SQLException {
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID="
+ + DEFAULT_CATALOG
+ + ";OAUTHTYPE=3;LOCATION=EU";
+
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ assertTrue(connection.isValid(1));
+
+ Statement statement = connection.createStatement();
+
+ // Query a dataset in the EU
+ String query =
+ "SELECT name FROM `bigquery-public-data.covid19_italy_eu.data_by_province` LIMIT 100";
+ ResultSet resultSet = statement.executeQuery(query);
+ assertEquals(100, resultSetRowCount(resultSet));
+
+ String connection_uri_null_location =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID="
+ + DEFAULT_CATALOG
+ + ";OAUTHTYPE=3";
+
+ Connection connection2 =
+ DriverManager.getConnection(connection_uri_null_location, new Properties());
+ assertNotNull(connection2);
+ assertTrue(connection2.isValid(1));
+ connection.close();
+
+ connection2.close();
+ }
+
+ @Test
+ public void testIncorrectLocation() throws SQLException {
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID="
+ + DEFAULT_CATALOG
+ + ";OAUTHTYPE=3;LOCATION=europe-west3";
+
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ assertTrue(connection.isValid(1));
+
+ // Query a dataset in the US
+ Statement statement = connection.createStatement();
+ String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 180";
+ Exception ex = assertThrows(Exception.class, () -> statement.executeQuery(query));
+ assertNotNull(ex);
+ assertTrue(ex.getMessage().contains("Access Denied"));
+ connection.close();
+ }
+
+ @Test
+ public void testRollbackOnConnectionClosed() throws SQLException {
+ String TRANSACTION_TABLE = "EXT_JDBC_TRANSACTION_TABLE1" + random.nextInt(99);
+ 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 (15, 'Farhan', %s);",
+ DATASET, TRANSACTION_TABLE, randomNumber);
+ String updateQuery =
+ String.format(
+ "UPDATE %s.%s SET age = 12 WHERE age = %s;", DATASET, TRANSACTION_TABLE, randomNumber);
+ String selectQuery =
+ String.format("SELECT id, name, age FROM %s.%s WHERE id = 12;", DATASET, TRANSACTION_TABLE);
+
+ Connection connection1 =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection1.createStatement();
+ bigQueryStatement.execute(createTransactionTable);
+ Connection connection = DriverManager.getConnection(session_enabled_connection_uri);
+ connection.setAutoCommit(false);
+ Statement statement = connection.createStatement();
+
+ boolean status = statement.execute(insertQuery);
+ assertFalse(status);
+ int rows = statement.executeUpdate(updateQuery);
+ assertEquals(1, rows);
+ status = statement.execute(selectQuery);
+ assertTrue(status);
+ connection.close();
+
+ // Separate query to check if transaction rollback worked
+ ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertFalse(resultSet.next());
+
+ bigQueryStatement.execute(
+ String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TRANSACTION_TABLE));
+ connection.close();
+ }
+
+ @Test
+ public void testSingleStatementTransaction() throws SQLException {
+ String TRANSACTION_TABLE = "EXT_JDBC_TRANSACTION_TABLE2" + random.nextInt(99);
+ String createTransactionTable =
+ String.format(
+ "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);",
+ DATASET, TRANSACTION_TABLE);
+ String beginTransaction = "BEGIN TRANSACTION; ";
+ String insertQuery =
+ String.format(
+ "INSERT INTO %s.%s (id, name, age) VALUES (12, 'Farhan', %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);
+ String commitTransaction = "COMMIT TRANSACTION;";
+
+ String transactionQuery =
+ beginTransaction
+ + insertQuery
+ + insertQuery
+ + updateQuery
+ + selectQuery
+ + commitTransaction;
+
+ Connection connection1 =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection1.createStatement();
+ bigQueryStatement.execute(createTransactionTable);
+
+ // Run the transaction
+ Connection connection = DriverManager.getConnection(session_enabled_connection_uri);
+ Statement statement = connection.createStatement();
+ statement.execute(transactionQuery);
+
+ // Test each query's result with getMoreResults
+ int resultsCount = 0;
+ boolean hasMoreResult = statement.getMoreResults();
+ while (hasMoreResult || statement.getUpdateCount() != -1) {
+ if (statement.getUpdateCount() == -1) {
+ ResultSet result = statement.getResultSet();
+ assertTrue(result.next());
+ assertEquals(-1, statement.getUpdateCount());
+ } else {
+ assertTrue(statement.getUpdateCount() > -1);
+ }
+ hasMoreResult = statement.getMoreResults();
+ resultsCount++;
+ }
+
+ // Check the transaction was actually committed.
+ ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery);
+ int rowCount = 0;
+ while (resultSet.next()) {
+ rowCount++;
+ assertEquals(14, resultSet.getInt(3));
+ }
+ assertEquals(2, rowCount);
+
+ bigQueryStatement.execute(
+ String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TRANSACTION_TABLE));
+ connection.close();
+ }
+
+ @Test
+ public void testConnectionWithMultipleTransactionCommits() throws SQLException {
+ String TRANSACTION_TABLE = "EXT_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);
+
+ Connection setupConnection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement setupStatement = setupConnection.createStatement();
+ setupStatement.execute(createTransactionTable);
+ setupStatement.close();
+ setupConnection.close();
+
+ 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 a separate connection
+ Connection verifyConnection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement verifyStatement = verifyConnection.createStatement();
+ ResultSet resultSet = verifyStatement.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 =
+ verifyStatement.executeQuery(
+ String.format("SELECT * FROM %s.%s WHERE id=15", DATASET, TRANSACTION_TABLE));
+ assertFalse(rs2.next());
+
+ verifyStatement.execute(
+ String.format("DROP TABLE IF EXISTS %s.%s", DATASET, TRANSACTION_TABLE));
+
+ verifyStatement.close();
+ verifyConnection.close();
+ statement.close();
+ connection.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);
+
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ 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));
+ connection.close();
+ }
+
+ @Test
+ public void testMultiStatementTransactionRollbackByUser() throws SQLException {
+ String TRANSACTION_TABLE = "EXT_JDBC_TRANSACTION_TABLE3" + random.nextInt(99);
+ 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, 'Farhan', %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);
+
+ Connection connection1 =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection1.createStatement();
+ bigQueryStatement.execute(createTransactionTable);
+
+ Connection connection = DriverManager.getConnection(session_enabled_connection_uri);
+ connection.setAutoCommit(false);
+ Statement statement = connection.createStatement();
+
+ boolean status = statement.execute(insertQuery);
+ assertFalse(status);
+ int rows = statement.executeUpdate(updateQuery);
+ assertEquals(1, rows);
+ status = statement.execute(selectQuery);
+ assertTrue(status);
+ connection.rollback();
+
+ // Separate query to check if transaction rollback worked
+ ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertFalse(resultSet.next());
+
+ bigQueryStatement.execute(
+ String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TRANSACTION_TABLE));
+ connection.close();
+ }
+
+ @Test
+ public void testMultiStatementTransactionDoesNotCommitWithoutCommit() throws SQLException {
+ String TRANSACTION_TABLE = "EXT_JDBC_TRANSACTION_TABLE4" + random.nextInt(99);
+ 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, 'Farhan', %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);
+
+ Connection connection1 =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection1.createStatement();
+ bigQueryStatement.execute(createTransactionTable);
+ Connection connection = DriverManager.getConnection(session_enabled_connection_uri);
+ connection.setAutoCommit(false);
+ Statement statement = connection.createStatement();
+
+ boolean status = statement.execute(insertQuery);
+ assertFalse(status);
+ int rows = statement.executeUpdate(updateQuery);
+ assertEquals(1, rows);
+ status = statement.execute(selectQuery);
+ assertTrue(status);
+
+ // Separate query to check nothing committed
+ ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertFalse(resultSet.next());
+
+ bigQueryStatement.execute(
+ String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TRANSACTION_TABLE));
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testValidMultiStatementTransactionCommits() throws SQLException {
+ String TRANSACTION_TABLE = "EXT_JDBC_TRANSACTION_TABLE5" + random.nextInt(99);
+ 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, 'Farhan', %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);
+
+ Connection connection1 =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection1.createStatement();
+ bigQueryStatement.execute(createTransactionTable);
+ Connection connection = DriverManager.getConnection(session_enabled_connection_uri);
+ connection.setAutoCommit(false);
+ Statement statement = connection.createStatement();
+
+ boolean status = statement.execute(insertQuery);
+ assertFalse(status);
+ status = statement.execute(updateQuery);
+ assertFalse(status);
+ status = statement.execute(selectQuery);
+ assertTrue(status);
+ connection.commit();
+
+ // Separate query to check inserted and updated data committed
+ ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertTrue(resultSet.next());
+ assertEquals(14, resultSet.getInt(3));
+
+ bigQueryStatement.execute(
+ String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TRANSACTION_TABLE));
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testTransactionRollbackOnError() throws SQLException {
+ String TRANSACTION_TABLE = "EXT_JDBC_TRANSACTION_TABLE6" + random.nextInt(99);
+ String createTransactionTable =
+ String.format(
+ "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);",
+ DATASET, TRANSACTION_TABLE);
+ String selectQuery =
+ String.format("SELECT id, name, age FROM %s.%s ;", DATASET, TRANSACTION_TABLE);
+
+ Connection connection1 =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection1.createStatement();
+ bigQueryStatement.execute(createTransactionTable);
+ String transactionOnError =
+ "BEGIN\n"
+ + "\n"
+ + " BEGIN TRANSACTION;\n"
+ + " INSERT INTO "
+ + DATASET
+ + "."
+ + TRANSACTION_TABLE
+ + "\n"
+ + " VALUES (39, 'Drake', 123);\n"
+ + " SELECT 1/0;\n"
+ + " COMMIT TRANSACTION;\n"
+ + "\n"
+ + "EXCEPTION WHEN ERROR THEN\n"
+ + " SELECT @@error.message;\n"
+ + " ROLLBACK TRANSACTION;\n"
+ + "END;";
+ Connection connection = DriverManager.getConnection(session_enabled_connection_uri);
+ Statement statement = connection.createStatement();
+ statement.execute(transactionOnError);
+
+ // do a check to see if no vals inserted
+ ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertFalse(resultSet.next());
+
+ bigQueryStatement.execute(
+ String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TRANSACTION_TABLE));
+ connection.close();
+ connection1.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="
+ + DEFAULT_CATALOG
+ + ";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 testExecuteUpdate() throws SQLException {
+ String TABLE_NAME = "EXT_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);
+
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ 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));
+ connection.close();
+ }
+
+ @Test
+ public void testExecuteMethod() throws SQLException {
+
+ String TABLE_NAME = "EXT_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);
+
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ 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);
+ connection.close();
+ }
+
+ @Test
+ public void testPreparedExecuteMethod() throws SQLException {
+
+ String TABLE_NAME = "EXT_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 StringField FROM `%s.%s`", DATASET, TABLE_NAME);
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ boolean createStatus = bigQueryStatement.execute(createQuery);
+ assertFalse(createStatus);
+
+ PreparedStatement insertStmt = connection.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);
+
+ Statement selectStmt = connection.createStatement();
+ boolean selectStatus = selectStmt.execute(selectQuery);
+ assertTrue(selectStatus);
+
+ int selectCount = selectStmt.getUpdateCount();
+ assertEquals(-1, selectCount);
+ ResultSet resultSet = selectStmt.getResultSet();
+ assertNotNull(resultSet);
+
+ PreparedStatement updateStmt = connection.prepareStatement(updateQuery);
+ updateStmt.setString(1, "Jane Doe");
+ updateStmt.setInt(2, 222);
+ boolean updateStatus = updateStmt.execute();
+ assertFalse(updateStatus);
+
+ boolean dropStatus = bigQueryStatement.execute(dropQuery);
+ assertFalse(dropStatus);
+ connection.close();
+ }
+
+ @Test
+ public void testPreparedStatementThrowsJdbcException() throws SQLException {
+ String TABLE_NAME = "EXT_JDBC_PREPARED_MISSING_PARAM_TABLE_" + randomNumber;
+ String createQuery =
+ String.format(
+ "CREATE OR REPLACE TABLE %s.%s (StringField STRING, IntegerField INTEGER);",
+ DATASET, TABLE_NAME);
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement bigQueryStatement = connection.createStatement();
+ boolean createStatus = bigQueryStatement.execute(createQuery);
+ assertFalse(createStatus);
+
+ String insertQuery =
+ String.format(
+ "INSERT INTO %s.%s (StringField, IntegerField) " + "VALUES (?,?), (?,?);",
+ DATASET, TABLE_NAME);
+ PreparedStatement insertStmt = connection.prepareStatement(insertQuery);
+ insertStmt.setString(1, "String1");
+ insertStmt.setInt(2, 111);
+ assertThrows(SQLException.class, insertStmt::execute);
+
+ bigQueryStatement.execute(String.format("DROP TABLE IF EXISTS %S.%s", DATASET, TABLE_NAME));
+ connection.close();
+ }
+
+ @Test
+ public void testValidExecuteBatch() throws SQLException {
+ // setup
+ String BATCH_TABLE = "EXT_JDBC_EXECUTE_BATCH_TABLE_" + random.nextInt(99);
+ String createBatchTable =
+ String.format(
+ "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);",
+ DATASET, BATCH_TABLE);
+
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement statement = connection.createStatement();
+ statement.execute(createBatchTable);
+ // act
+ // batch bypasses the 16 concurrent limit
+ int[] results;
+ for (int i = 0; i < 3; i++) {
+ String insertQuery =
+ "INSERT INTO "
+ + DATASET
+ + "."
+ + BATCH_TABLE
+ + " (id, name, age) "
+ + "VALUES (12, 'Farhan', "
+ + randomNumber
+ + i
+ + "); ";
+ statement.addBatch(insertQuery);
+ }
+ results = statement.executeBatch();
+
+ // assertions
+ assertEquals(3, results.length);
+ for (int updateCount : results) {
+ assertEquals(1, updateCount);
+ }
+ statement.execute(String.format("DROP TABLE IF EXISTS %S.%s", DATASET, BATCH_TABLE));
+ connection.close();
+ }
+
+ @Test
+ public void testAddBatchWithoutSemicolon() throws SQLException {
+ // setup
+ String BATCH_TABLE = "EXT_JDBC_EXECUTE_BATCH_TABLE_MISSING_SEMICOLON_" + random.nextInt(99);
+ String createBatchTable =
+ String.format(
+ "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);",
+ DATASET, BATCH_TABLE);
+
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement statement = connection.createStatement();
+ statement.execute(createBatchTable);
+
+ // act
+ // batch bypasses the 16 concurrent limit
+ String insertQuery =
+ "INSERT INTO "
+ + DATASET
+ + "."
+ + BATCH_TABLE
+ + " (id, name, age) "
+ + "VALUES (12, 'Farhan', 4)";
+ statement.addBatch(insertQuery);
+ statement.addBatch(insertQuery);
+ int[] results = statement.executeBatch();
+
+ // assertions
+ assertEquals(2, results.length);
+ for (int updateCount : results) {
+ assertEquals(1, updateCount);
+ }
+ statement.execute(String.format("DROP TABLE IF EXISTS %S.%s", DATASET, BATCH_TABLE));
+ connection.close();
+ connection.close();
+ }
+
+ // Array and Struct not tested.
+ @Test
+ public void testValidAllDataTypesSerializationFromSelectQuery() throws SQLException {
+ String DATASET = "JDBC_INTEGRATION_DATASET";
+ String TABLE_NAME = "JDBC_DATATYPES_INTEGRATION_TEST_TABLE";
+ String selectQuery = "select * from " + DATASET + "." + TABLE_NAME;
+
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement bigQueryStatement = connection.createStatement();
+ ResultSet resultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertNotNull(resultSet);
+ ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+ resultSet.next();
+ assertEquals(16, resultSetMetaData.getColumnCount());
+ assertTrue(resultSet.getBoolean(1));
+ assertEquals(33, resultSet.getInt(2));
+ assertEquals(50.05f, resultSet.getFloat(3), 0.0);
+ assertEquals(123.456, resultSet.getDouble(4), 0.0);
+ assertEquals(123.456789, resultSet.getDouble(5), 0.0);
+ assertEquals("testString", resultSet.getString(6));
+ assertEquals("Test String", new String(resultSet.getBytes(7), StandardCharsets.UTF_8));
+ assertEquals(Timestamp.valueOf("2020-04-27 18:07:25.356456"), resultSet.getObject(10));
+ assertEquals(Date.valueOf("2019-1-12"), resultSet.getObject(11));
+ assertEquals(Time.valueOf("14:00:00"), resultSet.getObject(12));
+ assertEquals(Timestamp.valueOf("2019-02-17 11:24:00.0"), resultSet.getObject(13));
+ assertEquals("POINT(1 2)", resultSet.getString(14));
+ assertEquals("{\"class\":{\"students\":[{\"name\":\"Jane\"}]}}", resultSet.getString(15));
+ assertEquals("123-7 -19 0:24:12.000006", resultSet.getString(16));
+ connection.close();
+ }
+
+ // Array and Struct not tested.
+ @Test
+ public void testValidAllDataTypesSerializationFromSelectQueryArrowDataset() throws SQLException {
+ String DATASET = "JDBC_INTEGRATION_DATASET";
+ String TABLE_NAME = "JDBC_INTEGRATION_ARROW_TEST_TABLE";
+ String selectQuery = "select * from " + DATASET + "." + TABLE_NAME + " LIMIT 5000;";
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;ProjectId="
+ + DEFAULT_CATALOG
+ + ";EnableHighThroughputAPI=1;"
+ + "HighThroughputActivationRatio=2;"
+ + "HighThroughputMinTableSize=1000;";
+
+ // 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(15, resultSetMetaData.getColumnCount());
+ assertTrue(resultSet.getBoolean(1));
+ assertEquals(33, resultSet.getInt(2));
+ assertEquals(50.05f, resultSet.getFloat(3), 0.0);
+ assertEquals(123.456, resultSet.getDouble(4), 0.0);
+ assertEquals(123.456789, resultSet.getDouble(5), 0.0);
+ assertEquals("testString", resultSet.getString(6));
+ assertEquals("Test String", new String(resultSet.getBytes(7), StandardCharsets.UTF_8));
+ assertEquals(Timestamp.valueOf("2020-04-27 18:07:25.356"), resultSet.getObject(10));
+ assertEquals(Timestamp.valueOf("2020-04-27 18:07:25.356"), resultSet.getTimestamp(10));
+ assertEquals(Date.valueOf("2019-1-12"), resultSet.getObject(11));
+ assertEquals(Date.valueOf("2019-1-12"), resultSet.getDate(11));
+ assertEquals(Time.valueOf("14:00:00"), resultSet.getObject(12));
+ assertEquals(Time.valueOf("14:00:00"), resultSet.getTime(12));
+ assertEquals(Timestamp.valueOf("2022-01-22 22:22:12.142265"), resultSet.getObject(13));
+ assertEquals("POINT(1 2)", resultSet.getString(14));
+ assertEquals("{\"class\":{\"students\":[{\"name\":\"Jane\"}]}}", resultSet.getString(15));
+ connection.close();
+ 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="
+ + DEFAULT_CATALOG
+ + ";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();
+ connection.close();
+ }
+
+ @Test
+ public void testValidLEPEndpointQuery() throws SQLException, InterruptedException {
+ 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="
+ + DEFAULT_CATALOG
+ + ";"
+ + "EndpointOverrides=BIGQUERY=https://us-east4-bigquery.googleapis.com;";
+
+ // Read data via JDBC
+ Connection connection = DriverManager.getConnection(connection_uri);
+ JdbcConnectionHelper.setUpDataset(DATASET);
+ Statement statement = connection.createStatement();
+ ResultSet resultSet = statement.executeQuery(selectQuery);
+ assertNotNull(resultSet.getMetaData());
+ connection.close();
+ }
+
+ @Test
+ public void testValidEndpointWithInvalidBQPortThrows() throws SQLException {
+ String TABLE_NAME = "EXT_JDBC_REGIONAL_TABLE_" + randomNumber;
+ String selectQuery = "select * from " + DATASET + "." + TABLE_NAME;
+ String connection_uri =
+ "jdbc:bigquery://https://googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";"
+ + "EndpointOverrides=BIGQUERY=https://us-east4-bigquery.googleapis.com:12312312;";
+
+ // Read data via JDBC
+ Connection connection = DriverManager.getConnection(connection_uri);
+ Statement statement = connection.createStatement();
+ assertThrows(SQLException.class, () -> statement.executeQuery(selectQuery));
+ connection.close();
+ }
+
+ @Test
+ public void testValidREPEndpointQuery() throws SQLException, InterruptedException {
+ 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="
+ + DEFAULT_CATALOG
+ + ";"
+ + "EndpointOverrides=BIGQUERY=https://bigquery.us-east4.rep.googleapis.com;";
+
+ // Read data via JDBC
+ Connection connection = DriverManager.getConnection(connection_uri);
+ JdbcConnectionHelper.setUpDataset(DATASET);
+ 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";
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement statement = connection.createStatement();
+ ResultSet jsonResultSet = statement.executeQuery(query);
+ assertEquals(10, resultSetRowCount(jsonResultSet));
+ statement.close();
+ assertTrue(statement.isClosed());
+ connection.close();
+ }
+
+ @Test
+ public void testPreparedStatementSmallSelect() throws SQLException {
+ String query =
+ "SELECT * FROM `bigquery-public-data.samples.github_timeline` where repository_language=?"
+ + " LIMIT 1000";
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ PreparedStatement preparedStatement = connection.prepareStatement(query);
+ preparedStatement.setString(1, "Java");
+
+ ResultSet jsonResultSet = preparedStatement.executeQuery();
+
+ int rowCount = resultSetRowCount(jsonResultSet);
+ assertEquals(1000, rowCount);
+ connection.close();
+ }
+
+ @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);
+
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement bigQueryStatement = connection.createStatement();
+ 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 = connection.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 = connection.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 = connection.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 = connection.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));
+ connection.close();
+ }
+
+ @Test
+ public void testUseLegacySQLWithLargeResultsNotAllowedQueries() throws SQLException {
+ // setup
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryDialect=BIG_QUERY;AllowLargeResults=0;";
+ String selectLegacyQuery =
+ "SELECT * FROM [bigquery-public-data.deepmind_alphafold.metadata] LIMIT 250000;";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act
+ ResultSet resultSet = statement.executeQuery(selectLegacyQuery);
+
+ // assertion
+ assertNotNull(resultSet);
+ connection.close();
+ }
+
+ @Test
+ public void testUseLegacySQLWithLargeResultsAllowedWithNoDestinationTableDefaults()
+ throws SQLException {
+ // setup
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryDialect=BIG_QUERY;AllowLargeResults=1;";
+ String selectLegacyQuery =
+ "SELECT * FROM [bigquery-public-data.deepmind_alphafold.metadata] LIMIT 250000;";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act
+ ResultSet resultSet = statement.executeQuery(selectLegacyQuery);
+
+ // assertion
+ assertNotNull(resultSet);
+ connection.close();
+ }
+
+ @Test
+ public void testValidDestinationTableSavesQueriesWithLegacySQL() throws SQLException {
+ // setup
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryDialect=BIG_QUERY;"
+ + "AllowLargeResults=1;"
+ + "LargeResultTable=destination_table_test_legacy;"
+ + "LargeResultDataset=INTEGRATION_TESTS;";
+ String selectLegacyQuery =
+ "SELECT * FROM [bigquery-public-data.deepmind_alphafold.metadata] LIMIT 200;";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act
+ ResultSet resultSet = statement.executeQuery(selectLegacyQuery);
+
+ // assertion
+ assertNotNull(resultSet);
+ String selectQuery = "SELECT * FROM `INTEGRATION_TESTS.destination_table_test_legacy`;";
+ Connection connection1 =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement bigQueryStatement = connection1.createStatement();
+ ResultSet actualResultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertTrue(0 < resultSetRowCount(actualResultSet));
+
+ // clean up
+ String deleteRows = "DELETE FROM `INTEGRATION_TESTS.destination_table_test_legacy` WHERE 1=1;";
+ bigQueryStatement.execute(deleteRows);
+ connection.close();
+ }
+
+ @Test
+ public void testValidDestinationTableSavesQueriesWithStandardSQL() throws SQLException {
+ // setup
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryDialect=SQL;"
+ + "LargeResultTable=destination_table_test;"
+ + "LargeResultDataset=INTEGRATION_TESTS;";
+ String selectLegacyQuery =
+ "SELECT * FROM `bigquery-public-data.deepmind_alphafold.metadata` LIMIT 200;";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act
+ ResultSet resultSet = statement.executeQuery(selectLegacyQuery);
+
+ // assertion
+ assertNotNull(resultSet);
+ String selectQuery = "SELECT * FROM INTEGRATION_TESTS.destination_table_test;";
+ Connection connection1 =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement bigQueryStatement = connection1.createStatement();
+ ResultSet actualResultSet = bigQueryStatement.executeQuery(selectQuery);
+ assertEquals(200, resultSetRowCount(actualResultSet));
+
+ // clean up
+ String deleteRows = "DELETE FROM `INTEGRATION_TESTS.destination_table_test` WHERE 1=1;";
+ bigQueryStatement.execute(deleteRows);
+ connection.close();
+ }
+
+ @Test
+ public void testDestinationTableAndDestinationDatasetThatDoesNotExistsCreates()
+ throws SQLException {
+ // setup
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryDialect=BIG_QUERY;"
+ + "AllowLargeResults=1;"
+ + "LargeResultTable=FakeTable;"
+ + "LargeResultDataset=FakeDataset;";
+ String selectLegacyQuery =
+ "SELECT * FROM [bigquery-public-data.deepmind_alphafold.metadata] LIMIT 200;";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act
+ ResultSet resultSet = statement.executeQuery(selectLegacyQuery);
+
+ // assertion
+ assertNotNull(resultSet);
+ String separateQuery = "SELECT * FROM FakeDataset.FakeTable;";
+ Connection connection1 =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement bigQueryStatement = connection1.createStatement();
+ boolean result = bigQueryStatement.execute(separateQuery);
+ assertTrue(result);
+
+ // clean up
+ bigQueryStatement.execute("DROP SCHEMA FakeDataset CASCADE;");
+ connection.close();
+ }
+
+ @Test
+ public void testNonSelectForStandardDestinationTableDoesNotThrow() throws SQLException {
+ // setup
+ String TRANSACTION_TABLE = "EXT_JDBC_TRANSACTION_TABLE8" + random.nextInt(99);
+ String createTransactionTable =
+ String.format(
+ "CREATE OR REPLACE TABLE %s.%s (`id` INTEGER, `name` STRING, `age` INTEGER);",
+ DATASET, TRANSACTION_TABLE);
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryDialect=SQL;"
+ + "AllowLargeResults=1;"
+ + "LargeResultTable=destination_table_test;"
+ + "LargeResultDataset=INTEGRATION_TESTS;";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act & assertion
+ statement.execute(createTransactionTable);
+ connection.close();
+ }
+
+ @Test
+ public void testNonEnabledUseLegacySQLThrowsSyntaxError() throws SQLException {
+ // setup
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";";
+ String selectLegacyQuery =
+ "SELECT * FROM [bigquery-public-data.deepmind_alphafold.metadata] LIMIT 20000000;";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act & assertion
+ assertThrows(SQLException.class, () -> statement.execute(selectLegacyQuery));
+ connection.close();
+ }
+
+ // result order is wrong here, simba will fix this in a future release
+ // b/356365421
+ @Test
+ @Ignore
+ public void testTableConstraints() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ ResultSet primaryKey1 =
+ connection
+ .getMetaData()
+ .getPrimaryKeys(DEFAULT_CATALOG, CONSTRAINTS_DATASET, CONSTRAINTS_TABLE_NAME);
+ primaryKey1.next();
+ assertEquals("id", primaryKey1.getString(4));
+ assertFalse(primaryKey1.next());
+
+ ResultSet primaryKey2 =
+ connection
+ .getMetaData()
+ .getPrimaryKeys(DEFAULT_CATALOG, 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 =
+ connection
+ .getMetaData()
+ .getImportedKeys(DEFAULT_CATALOG, 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 =
+ connection
+ .getMetaData()
+ .getCrossReference(
+ DEFAULT_CATALOG,
+ CONSTRAINTS_DATASET,
+ CONSTRAINTS_TABLE_NAME2,
+ DEFAULT_CATALOG,
+ 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));
+ connection.close();
+ }
+
+ @Test
+ public void testDatabaseMetadataGetProcedures() throws SQLException {
+ String DATASET = "JDBC_INTEGRATION_DATASET";
+ String procedureName = "create_customer";
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ DatabaseMetaData databaseMetaData = connection.getMetaData();
+ ResultSet resultSet = databaseMetaData.getProcedures(DEFAULT_CATALOG, DATASET, procedureName);
+ while (resultSet.next()) {
+ assertEquals(DEFAULT_CATALOG, resultSet.getString("PROCEDURE_CAT"));
+ assertEquals(DATASET, resultSet.getString("PROCEDURE_SCHEM"));
+ assertEquals(procedureName, resultSet.getString("PROCEDURE_NAME"));
+ assertEquals(procedureName, resultSet.getString("SPECIFIC_NAME"));
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testDatabaseMetadataGetProcedureColumns() throws SQLException {
+ String DATASET = "JDBC_INTEGRATION_DATASET";
+ String procedureName = "create_customer";
+ String parameterName = "id";
+
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ DatabaseMetaData databaseMetaData = connection.getMetaData();
+ ResultSet resultSet =
+ databaseMetaData.getProcedureColumns(
+ DEFAULT_CATALOG, DATASET, procedureName, parameterName);
+ while (resultSet.next()) {
+ assertEquals(DEFAULT_CATALOG, resultSet.getString("PROCEDURE_CAT"));
+ assertEquals(DATASET, resultSet.getString("PROCEDURE_SCHEM"));
+ assertEquals(procedureName, resultSet.getString("PROCEDURE_NAME"));
+ assertEquals(procedureName, resultSet.getString("SPECIFIC_NAME"));
+ assertEquals(parameterName, resultSet.getString("COLUMN_NAME"));
+ assertEquals(65535, resultSet.getInt("PRECISION"));
+ assertEquals(2, resultSet.getShort("NULLABLE"));
+ }
+ connection.close();
+ }
+
+ @Test
+ public void testAlterTable() throws SQLException {
+ String TABLE_NAME = "EXT_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);
+
+ Connection connection =
+ DriverManager.getConnection(
+ String.format(connectionUrl, DEFAULT_CATALOG), new Properties());
+ Statement bigQueryStatement = connection.createStatement();
+ 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));
+ connection.close();
+ }
+
+ @Test
+ public void testQueryPropertyDataSetProjectIdQueriesToIncorrectDatasetThrows()
+ throws SQLException {
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryProperties=dataset_project_id=bigquerytestdefault"
+ + ";";
+ String insertQuery =
+ String.format(
+ "INSERT INTO %s.%s (id, name, age) VALUES (15, 'Farhan', 25);",
+ "INTEGRATION_TESTS", "Test_Table");
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act & assertion
+ assertThrows(SQLException.class, () -> statement.execute(insertQuery));
+ connection.close();
+ }
+
+ @Test
+ public void testQueryPropertyTimeZoneQueries() throws SQLException {
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;"
+ + "OAuthType=3;"
+ + "ProjectId="
+ + DEFAULT_CATALOG
+ + ";QueryProperties=time_zone=America/New_York;";
+ String query = "SELECT * FROM `bigquery-public-data.samples.github_timeline` LIMIT 180";
+ Connection connection = DriverManager.getConnection(connection_uri, new Properties());
+ Statement statement = connection.createStatement();
+
+ // act
+ ResultSet resultSet = statement.executeQuery(query);
+
+ // assertions
+ assertNotNull(resultSet);
+ assertTrue(resultSet.next());
+ connection.close();
+ }
+
+ private int resultSetRowCount(ResultSet resultSet) throws SQLException {
+ int rowCount = 0;
+ while (resultSet.next()) {
+ rowCount++;
+ }
+ return rowCount;
+ }
+}
diff --git a/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/statement/ITJdbcStatementTest.java b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/statement/ITJdbcStatementTest.java
new file mode 100644
index 000000000000..ba992a970c34
--- /dev/null
+++ b/java-bigquery/google-cloud-bigquery-jdbc/bigquery-external-jdbc-tests/src/test/java/com/google/cloud/bigquery/jdbc/javatests/statement/ITJdbcStatementTest.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2026 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.javatests.statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+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.bigquery.jdbc.javatests.JdbcConnectionHelper;
+import com.google.cloud.ServiceOptions;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLDataException;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Statement;
+import java.util.Properties;
+import java.util.Random;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ITJdbcStatementTest {
+ private static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId();
+ private static final String DATASET = "JDBC_STATEMENT_TEST_DATASET";
+ private static String connectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId=%s;OAuthType=3;Timeout=3600;";
+ private static Random random = new Random();
+ private static int randomNumber = random.nextInt(999);
+ private static final String TABLE_NAME = "JDBC_STATEMENT_TEST_TABLE" + randomNumber;
+
+ @BeforeClass
+ public static void beforeClass() throws InterruptedException {
+ JdbcConnectionHelper.setUpDataset(DATASET);
+ JdbcConnectionHelper.setUpTable(DATASET, TABLE_NAME);
+ }
+
+ @AfterClass
+ public static void afterClass() throws InterruptedException {
+ JdbcConnectionHelper.cleanUp(DATASET);
+ }
+
+ @Test
+ public void testExecute() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+ String selectQuery = "select * from " + DATASET + "." + TABLE_NAME;
+
+ Boolean selectQueryReturn = statement.execute(selectQuery);
+ assertEquals(true, selectQueryReturn);
+
+ ResultSet resultSet = statement.getResultSet();
+ while (resultSet.next()) {
+ if (resultSet.getInt("IntegerField") == 111) {
+ assertEquals("string1", resultSet.getString("StringField"));
+ assertEquals("737472696E6731", resultSet.getString("BytesField"));
+ }
+ }
+
+ assertEquals(0, statement.getQueryTimeout());
+ assertEquals(ResultSet.FETCH_FORWARD, statement.getFetchDirection());
+ assertNotEquals(ResultSet.FETCH_REVERSE, statement.getFetchDirection());
+ assertNotEquals(ResultSet.FETCH_UNKNOWN, statement.getFetchDirection());
+
+ assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, statement.getResultSetHoldability());
+ assertNotEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
+
+ assertEquals(ResultSet.TYPE_FORWARD_ONLY, statement.getResultSetType());
+ assertNotEquals(ResultSet.TYPE_SCROLL_SENSITIVE, statement.getResultSetType());
+ assertNotEquals(ResultSet.TYPE_SCROLL_INSENSITIVE, statement.getResultSetType());
+
+ assertEquals(ResultSet.CONCUR_READ_ONLY, statement.getResultSetConcurrency());
+ assertNotEquals(ResultSet.CONCUR_UPDATABLE, statement.getResultSetConcurrency());
+
+ assertEquals(-1, statement.getUpdateCount());
+ // assertEquals(-1L, statement.getLargeUpdateCount());
+
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testExecuteQuery() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+ String selectQuery =
+ "SELECT * FROM bigquery-public-data.chicago_taxi_trips.taxi_trips LIMIT 1000;";
+
+ ResultSet selectQueryResult = statement.executeQuery(selectQuery);
+ ResultSet statementResult = statement.getResultSet();
+ assertEquals(statementResult, selectQueryResult);
+
+ assertEquals(0, statement.getFetchSize());
+
+ // setMaxRows Test
+ statement.setMaxRows(5);
+ ResultSet maxRowsResultSet = statement.executeQuery(selectQuery);
+ assertEquals(5, getSizeOfResultSet(maxRowsResultSet));
+
+ try {
+ statement.setMaxRows(0);
+ maxRowsResultSet = statement.executeQuery(selectQuery);
+ } catch (SQLDataException exception) {
+ assertTrue(true);
+ statement.close();
+ }
+
+ // TODO(note): setFetchSize not working
+ /*statement.setFetchSize(1);
+ ResultSet newResultSet = statement.executeQuery(selectQuery);
+ assertEquals(1, getSizeOfResultSet(newResultSet));*/
+
+ assertEquals(0, statement.getQueryTimeout());
+ assertEquals(ResultSet.FETCH_FORWARD, statement.getFetchDirection());
+ assertNotEquals(ResultSet.FETCH_REVERSE, statement.getFetchDirection());
+ assertNotEquals(ResultSet.FETCH_UNKNOWN, statement.getFetchDirection());
+ try {
+ statement.setFetchDirection(ResultSet.FETCH_REVERSE);
+ } catch (SQLFeatureNotSupportedException exception) {
+ assertTrue(true);
+ }
+
+ assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, statement.getResultSetHoldability());
+ assertNotEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, statement.getResultSetHoldability());
+
+ assertEquals(ResultSet.TYPE_FORWARD_ONLY, statement.getResultSetType());
+ assertNotEquals(ResultSet.TYPE_SCROLL_SENSITIVE, statement.getResultSetType());
+ assertNotEquals(ResultSet.TYPE_SCROLL_INSENSITIVE, statement.getResultSetType());
+
+ assertEquals(ResultSet.CONCUR_READ_ONLY, statement.getResultSetConcurrency());
+ assertNotEquals(ResultSet.CONCUR_UPDATABLE, statement.getResultSetConcurrency());
+
+ assertEquals(-1, statement.getUpdateCount());
+ assertEquals(-1L, statement.getLargeUpdateCount());
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testExecuteUpdate() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ String TEMP_TABLE_NAME = "TEMP_DDL_STATEMENT_TABLE";
+ String createQuery =
+ "CREATE OR REPLACE TABLE "
+ + DATASET
+ + "."
+ + TEMP_TABLE_NAME
+ + " (\n"
+ + "`StringField` STRING,\n"
+ + "`BytesField` BYTES,\n"
+ + "`IntegerField` INTEGER);";
+ String alterQuery =
+ "ALTER TABLE " + DATASET + "." + TEMP_TABLE_NAME + " ADD COLUMN `NewField` INTEGER;";
+ String updateQuery =
+ "UPDATE "
+ + DATASET
+ + "."
+ + TEMP_TABLE_NAME
+ + " SET StringField = 'string71' "
+ + " WHERE IntegerField = 333 "
+ + " ;";
+ String deleteQuery = "DROP TABLE " + DATASET + "." + TEMP_TABLE_NAME;
+ String multiInsertQuery =
+ "INSERT INTO "
+ + DATASET
+ + "."
+ + TEMP_TABLE_NAME
+ + " (\n"
+ + " StringField, BytesField,IntegerField)\n"
+ + " VALUES ('string3',CAST ('string3' AS BYTES),333), "
+ + " ('string4',CAST ('string4' AS BYTES),444), "
+ + " ('string5',CAST ('string5' AS BYTES),555) "
+ + " ;";
+ String selectQuery = "select * from " + DATASET + "." + TEMP_TABLE_NAME;
+
+ Statement statement = connection.createStatement();
+ int createCount = statement.executeUpdate(createQuery);
+ assertEquals(0, createCount);
+
+ int insertCount = statement.executeUpdate(multiInsertQuery);
+ assertEquals(3, insertCount);
+
+ int alterCount = statement.executeUpdate(alterQuery);
+ assertEquals(0, alterCount);
+ ResultSet resultSet = statement.executeQuery(selectQuery);
+ ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+ assertEquals(4, resultSetMetaData.getColumnCount());
+
+ int updateCount = statement.executeUpdate(updateQuery);
+ assertEquals(1, updateCount);
+
+ int deleteCount = statement.executeUpdate(deleteQuery);
+ assertEquals(0, deleteCount);
+
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testScript() throws SQLException {
+ String connection_uri =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;PROJECTID="
+ + DEFAULT_CATALOG
+ + ";OAUTHTYPE=3";
+ Properties withReadApi = new Properties();
+ withReadApi.setProperty("EnableHighThroughputAPI", "1");
+ Connection connection = DriverManager.getConnection(connection_uri, withReadApi);
+ Statement statement = connection.createStatement();
+ String BASE_QUERY =
+ "SELECT * FROM bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2017 order by"
+ + " trip_distance asc LIMIT %s;";
+ int expectedCnt = 500000;
+ String longQuery = String.format(BASE_QUERY, expectedCnt);
+ String longerQuery = String.format(BASE_QUERY, 700000);
+ String longerQuery2 = String.format(BASE_QUERY, 900000);
+ statement.execute(longQuery + longerQuery + longerQuery2);
+ ResultSet arrowResultSet = statement.getResultSet();
+ assertEquals(500000, resultSetRowCount(arrowResultSet));
+ arrowResultSet.close();
+ connection.close();
+ }
+
+ private int resultSetRowCount(ResultSet resultSet) throws SQLException {
+ int rowCount = 0;
+ while (resultSet.next()) {
+ rowCount++;
+ }
+ return rowCount;
+ }
+
+ @Test
+ public void testStringColumnLength() throws SQLException {
+ String projectId = DEFAULT_CATALOG;
+ String TABLE_NAME = "StringColumnLengthTable";
+ String oauthType = "3"; // Google Application Credentials
+ int length = 10;
+ String connectionUrl =
+ "jdbc:bigquery://https://www.googleapis.com/bigquery/v2:443;ProjectId="
+ + projectId
+ + ";OAuthType="
+ + oauthType
+ + ";Timeout=3600;"
+ + "StringColumnLength="
+ + length
+ + ";";
+ // + "EnableSession=1";
+ Connection connection1 = DriverManager.getConnection(connectionUrl);
+ Statement statement = connection1.createStatement();
+
+ String createQuery =
+ "CREATE OR REPLACE TABLE "
+ + DATASET
+ + "."
+ + TABLE_NAME
+ + " (\n"
+ + "`StringField1` STRING,\n"
+ + "`StringField2` STRING,\n"
+ + "`StringField3` STRING \n"
+ + ");";
+ String insertQuery =
+ "INSERT INTO %s.%s (StringField1, StringField2, StringField3) VALUES ('%s', '%s', '%s') ;";
+ String selectQuery = String.format("SELECT * FROM %s.%s; ", DATASET, TABLE_NAME);
+ statement.execute(createQuery);
+
+ String s1 = generateString(1111);
+ String s2 = generateString(11111);
+ String s3 = generateString(111111);
+ statement.execute(String.format(insertQuery, DATASET, TABLE_NAME, s1, s2, s3));
+ ResultSet rs = statement.executeQuery(selectQuery);
+ ResultSetMetaData metadata = rs.getMetaData();
+ int i = 0;
+ while (rs.next()) {
+ assertNotNull(rs.getString(1));
+ assertNotNull(rs.getString(2));
+ assertNotNull(rs.getString(3));
+ }
+ }
+
+ private String generateString(int len) {
+ StringBuilder s = new StringBuilder();
+ for (int i = 0; i < len; i++) {
+ s = s.append("s");
+ }
+ return s.toString();
+ }
+
+ @Test
+ public void testCloseStatement() throws SQLException {
+ String selectQuery = "select * from " + DATASET + "." + TABLE_NAME;
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+
+ assertFalse(statement.isClosed());
+ assertFalse(statement.isCloseOnCompletion());
+ statement.closeOnCompletion();
+ assertTrue(statement.isCloseOnCompletion());
+ ResultSet resultSet = statement.executeQuery(selectQuery);
+ resultSet.close();
+
+ assertTrue(statement.isClosed());
+ statement = connection.createStatement();
+ statement.close();
+ assertTrue(statement.isClosed());
+ connection.close();
+ }
+
+ @Test
+ public void testSetTimeout() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+
+ String selectQuery =
+ "SELECT views FROM bigquery-public-data.wikipedia.pageviews_2020 WHERE datehour >="
+ + " '2020-01-01' LIMIT 9000000";
+
+ // statement.execute(selectQuery);
+ assertEquals(0, statement.getQueryTimeout());
+ statement.setQueryTimeout(1);
+ assertEquals(1, statement.getQueryTimeout());
+ try {
+ statement.executeQuery(selectQuery);
+ } catch (SQLException e) {
+ assertTrue(true);
+ assertEquals("SQL execution canceled", e.getMessage());
+ }
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testSetTimeoutThrows() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+ assertThrows(SQLException.class, () -> statement.setQueryTimeout(-1));
+ }
+
+ @Test
+ public void testDefaultValues() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+ assertEquals(0, statement.getMaxFieldSize());
+ // assertEquals(0, statement.getLargeMaxRows());
+ assertNull(statement.getWarnings());
+ assertFalse(statement.isPoolable());
+ statement.close();
+ connection.close();
+ }
+
+ @Test
+ public void testRangeSelectDataset() throws SQLException {
+ Connection connection =
+ DriverManager.getConnection(String.format(connectionUrl, DEFAULT_CATALOG));
+ Statement statement = connection.createStatement();
+
+ // execute
+ try {
+ statement.execute(
+ "CREATE TABLE "
+ + DEFAULT_CATALOG
+ + "."
+ + DATASET
+ + ".RangeTable (x RANGE OPTIONS (description = 'An optional RANGE"
+ + " field'), y STRUCT > OPTIONS (description = 'An array of"
+ + " RANGE field')>);");
+ ResultSet selectQueryResult =
+ statement.executeQuery(
+ "SELECT * FROM " + String.format(" `%s.%s.RangeTable`;", DEFAULT_CATALOG, DATASET));
+
+ assertEquals(selectQueryResult.getMetaData().getColumnTypeName(1), "RANGE");
+ } finally {
+ // clean up
+ statement.execute(
+ String.format("DROP TABLE IF EXISTS %s.%s.RangeTable;", DEFAULT_CATALOG, DATASET));
+ statement.close();
+ }
+ connection.close();
+ }
+
+ int getSizeOfResultSet(ResultSet resultSet) throws SQLException {
+ int count = 0;
+ while (resultSet.next()) {
+ count++;
+ }
+ return count;
+ }
+}