diff --git a/README.md b/README.md index 2f72429bd..2fa380598 100644 --- a/README.md +++ b/README.md @@ -53,20 +53,20 @@ If you are using Maven without the BOM, add this to your dependencies: If you are using Gradle 5.x or later, add this to your dependencies: ```Groovy -implementation platform('com.google.cloud:libraries-bom:26.18.0') +implementation platform('com.google.cloud:libraries-bom:26.19.0') implementation 'com.google.cloud:google-cloud-bigquery' ``` If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-bigquery:2.30.0' +implementation 'com.google.cloud:google-cloud-bigquery:2.30.1' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-bigquery" % "2.30.0" +libraryDependencies += "com.google.cloud" % "google-cloud-bigquery" % "2.30.1" ``` @@ -351,7 +351,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-bigquery/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-bigquery.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-bigquery/2.30.0 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-bigquery/2.30.1 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java index d1bbcb5e2..ef7e8cb8b 100644 --- a/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java +++ b/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java @@ -55,7 +55,11 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.checkerframework.checker.nullness.qual.NonNull; +import org.threeten.bp.Instant; +import org.threeten.bp.temporal.ChronoUnit; final class BigQueryImpl extends BaseService implements BigQuery { @@ -422,15 +426,38 @@ public com.google.api.services.bigquery.model.Job call() { } if (!idRandom) { + if (createException instanceof BigQueryException && createException.getCause() != null) { + + /*GoogleJsonResponseException createExceptionCause = + (GoogleJsonResponseException) createException.getCause();*/ + + Pattern pattern = Pattern.compile(".*Already.*Exists:.*Job.*", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(createException.getCause().getMessage()); + + if (matcher.find()) { + // If the Job ALREADY EXISTS, retrieve it. + Job job = this.getJob(jobInfo.getJobId()); + + long jobCreationTime = job.getStatistics().getCreationTime(); + long jobMinStaleTime = System.currentTimeMillis(); + long jobMaxStaleTime = + Instant.ofEpochMilli(jobMinStaleTime).minus(1, ChronoUnit.DAYS).toEpochMilli(); + + // Only return the job if it has been created in the past 24 hours. + // This is assuming any job older than 24 hours is a valid duplicate JobID + // and not a false positive like b/290419183 + if (jobCreationTime >= jobMaxStaleTime && jobCreationTime <= jobMinStaleTime) { + return job; + } + } + } throw createException; } // If create RPC fails, it's still possible that the job has been successfully - // created, - // and get might work. + // created, and get might work. // We can only do this if we randomly generated the ID. Otherwise we might - // mistakenly - // fetch a job created by someone else. + // mistakenly fetch a job created by someone else. Job job; try { job = getJob(finalJobId[0]); diff --git a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java index 97db5fc20..8c5742e57 100644 --- a/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java +++ b/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java @@ -83,6 +83,7 @@ import com.google.cloud.bigquery.InsertAllRequest.RowToInsert; import com.google.cloud.bigquery.InsertAllResponse; import com.google.cloud.bigquery.Job; +import com.google.cloud.bigquery.JobConfiguration; import com.google.cloud.bigquery.JobId; import com.google.cloud.bigquery.JobInfo; import com.google.cloud.bigquery.JobStatistics; @@ -6101,4 +6102,31 @@ public void testForeignKeysUpdate() { bigquery.delete(tableIdPk2); } } + + @Test + public void testAlreadyExistJobExceptionHandling() throws InterruptedException { + String query = + "SELECT TimestampField, StringField, BooleanField FROM " + + DATASET + + "." + + TABLE_ID.getTable(); + JobId jobId = JobId.newBuilder().setRandomJob().build(); + + JobConfiguration queryJobConfiguration = QueryJobConfiguration.newBuilder(query).build(); + // Creating the job with the explicit jobID + bigquery.create(JobInfo.of(jobId, queryJobConfiguration)); + // Calling the query method with the job that has already been created. + // This should throw ALREADY_EXISTS error without the exception handling added + // or if the job is older than 24 hours. + try { + bigquery.query(QueryJobConfiguration.newBuilder(query).build(), jobId); + // Test succeeds if Exception is not thrown and code flow reaches this statement. + assertTrue(true); + } catch (BigQueryException ex) { + // test fails if an exception is thrown + if (ex.getCause() != null && ex.getCause().getMessage().contains("Already Exists: Job")) { + fail("Already exists error should not be thrown"); + } + } + } }