From 4d7e351b12fa29057e1791ebbd45548422ab1213 Mon Sep 17 00:00:00 2001 From: Sivamurugan P Date: Sun, 21 Dec 2025 16:43:38 +0530 Subject: [PATCH 1/4] fix: handled race condition in stateless query integration test The testTableResultJobIdAndQueryId test was failing intermittently on slower networks. The test strictly asserted that Job ID must be null for stateless queries. However, the library correctly falls back to creating a Job ID if the stateless query times out. This change updates the assertion logic to accept either a valid Query ID (stateless success) or a valid Job ID (fallback success). Fixes #4008 --- .../com/google/cloud/bigquery/it/ITBigQueryTest.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 80605884d..afc315065 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 @@ -7220,8 +7220,16 @@ public void testTableResultJobIdAndQueryId() throws InterruptedException { String query = "SELECT 1 as one"; QueryJobConfiguration configStateless = QueryJobConfiguration.newBuilder(query).build(); TableResult result = bigQuery.query(configStateless); - assertNull(result.getJobId()); - assertNotNull(result.getQueryId()); + // FIX START: Handle race condition where network latency causes fallback to Job creation. + if (result.getJobId() != null) { + // If the server forced a Job ID (due to latency/timeout), the Query ID should be null. + // This is valid fallback behavior as per the library contract. + assertNull(result.getQueryId()); + } else { + // If the query remained stateless, we expect a Query ID. + assertNotNull(result.getQueryId()); + } + // FIX END // Test scenario 2 by failing stateless check by setting job timeout. QueryJobConfiguration configQueryWithJob = From 10873a7823c179e856bec1c4b5ba3a722f5d2fa5 Mon Sep 17 00:00:00 2001 From: Sivamurugan P Date: Sun, 21 Dec 2025 17:07:25 +0530 Subject: [PATCH 2/4] refactor: use XOR assertion for conciseness Applied feedback from code review to use exclusive OR operator for validating JobID/QueryID mutual exclusivity. --- .../google/cloud/bigquery/it/ITBigQueryTest.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) 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 afc315065..5dfb2965b 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 @@ -7220,16 +7220,11 @@ public void testTableResultJobIdAndQueryId() throws InterruptedException { String query = "SELECT 1 as one"; QueryJobConfiguration configStateless = QueryJobConfiguration.newBuilder(query).build(); TableResult result = bigQuery.query(configStateless); - // FIX START: Handle race condition where network latency causes fallback to Job creation. - if (result.getJobId() != null) { - // If the server forced a Job ID (due to latency/timeout), the Query ID should be null. - // This is valid fallback behavior as per the library contract. - assertNull(result.getQueryId()); - } else { - // If the query remained stateless, we expect a Query ID. - assertNotNull(result.getQueryId()); - } - // FIX END + // A stateless query should result in either a queryId (stateless success) or a jobId (fallback to a job). + // Exactly one of them should be non-null. + assertTrue( + "Exactly one of jobId or queryId should be non-null", + (result.getJobId() != null) ^ (result.getQueryId() != null)); // Test scenario 2 by failing stateless check by setting job timeout. QueryJobConfiguration configQueryWithJob = From d24fc23b685644bd34864c827d4a28b94f4b2200 Mon Sep 17 00:00:00 2001 From: Sivamurugan P Date: Sun, 21 Dec 2025 18:16:44 +0530 Subject: [PATCH 3/4] fix: apply race condition logic to testStatelessQueries Applied XOR assertion logic to testStatelessQueries. Test was failing on slow networks because they did not account for JOB_CREATION_OPTIONAL falling back to job creation. Fixes #4002 --- .../com/google/cloud/bigquery/it/ITBigQueryTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 5dfb2965b..cabb6ad8e 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 @@ -7181,8 +7181,10 @@ public void testStatelessQueries() throws InterruptedException { // Stateless query should have no job id. bigQuery.getOptions().setDefaultJobCreationMode(JobCreationMode.JOB_CREATION_OPTIONAL); TableResult tableResult = executeSimpleQuery(bigQuery); - assertNotNull(tableResult.getQueryId()); - assertNull(tableResult.getJobId()); + // Use XOR: We accept EITHER a QueryId (fast path) OR a JobId (slow fallback), but not both. + assertTrue( + "Exactly one of jobId or queryId should be non-null", + (tableResult.getJobId() != null) ^ (tableResult.getQueryId() != null)); // Job creation takes over, no query id is created. bigQuery.getOptions().setDefaultJobCreationMode(JobCreationMode.JOB_CREATION_REQUIRED); @@ -7220,7 +7222,8 @@ public void testTableResultJobIdAndQueryId() throws InterruptedException { String query = "SELECT 1 as one"; QueryJobConfiguration configStateless = QueryJobConfiguration.newBuilder(query).build(); TableResult result = bigQuery.query(configStateless); - // A stateless query should result in either a queryId (stateless success) or a jobId (fallback to a job). + // A stateless query should result in either a queryId (stateless success) or a jobId (fallback + // to a job). // Exactly one of them should be non-null. assertTrue( "Exactly one of jobId or queryId should be non-null", From dfc7e2b0ea7e6eb5407d2c646bd24489b817af5f Mon Sep 17 00:00:00 2001 From: Sivamurugan P Date: Wed, 24 Dec 2025 21:17:53 +0530 Subject: [PATCH 4/4] docs: add comment explaining stateless query fallback behavior --- .../test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java | 1 + 1 file changed, 1 insertion(+) 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 cabb6ad8e..5acf9fc64 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 @@ -7182,6 +7182,7 @@ public void testStatelessQueries() throws InterruptedException { bigQuery.getOptions().setDefaultJobCreationMode(JobCreationMode.JOB_CREATION_OPTIONAL); TableResult tableResult = executeSimpleQuery(bigQuery); // Use XOR: We accept EITHER a QueryId (fast path) OR a JobId (slow fallback), but not both. + // Ideally Stateless query will return queryId but in some cases it would return jobId instead of queryId based on the query complexity or other factors (job timeout configs). assertTrue( "Exactly one of jobId or queryId should be non-null", (tableResult.getJobId() != null) ^ (tableResult.getQueryId() != null));