Skip to content

Commit 83297be

Browse files
committed
exception logging test
1 parent 7c9376b commit 83297be

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

java-bigquery/google-cloud-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/PerConnectionFileHandlerTest.java

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,20 @@
1616

1717
package com.google.cloud.bigquery.jdbc;
1818

19+
import static org.junit.jupiter.api.Assertions.assertNotNull;
20+
import static org.junit.jupiter.api.Assertions.assertNull;
21+
import static org.junit.jupiter.api.Assertions.assertThrows;
1922
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
import static org.mockito.Mockito.mock;
2024

25+
import com.google.cloud.bigquery.Field;
26+
import com.google.cloud.bigquery.FieldList;
27+
import com.google.cloud.bigquery.StandardSQLTypeName;
2128
import java.io.IOException;
2229
import java.nio.file.Files;
2330
import java.nio.file.Path;
31+
import java.sql.SQLException;
32+
import java.sql.Statement;
2433
import java.util.Optional;
2534
import java.util.logging.Level;
2635
import java.util.logging.LogRecord;
@@ -106,4 +115,104 @@ public void testCloseHandler() throws IOException {
106115
// File remains on disk, but handler is closed.
107116
assertTrue(connLog.isPresent());
108117
}
118+
119+
/**
120+
* Verifies that when an exception is thrown from within a dynamic proxy wrapper method while the
121+
* thread-local connection ID context is completely missing (null), the proxy's catch block
122+
* dynamically extracts the connection ID from the target instance, registers it on the executing
123+
* thread, and writes the severe exception log record directly to the connection-specific log file
124+
* (e.g. "BQ-JDBC-timestamp-c456.log") instead of the global log file.
125+
*/
126+
@Test
127+
public void testProxyExceptionLogRouting() throws Exception {
128+
// Register the temp file handler to the root logger so proxy logs are routed to it
129+
java.util.logging.Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger();
130+
rootLogger.addHandler(handler);
131+
132+
try {
133+
// Ensure thread context is completely missing (null) before query
134+
BigQueryJdbcMdc.clear();
135+
assertNull(BigQueryJdbcMdc.getConnectionId());
136+
137+
// Create a mock statement with connectionId = "c456"
138+
BigQueryStatement mockStmt = mock(BigQueryStatement.class);
139+
mockStmt.connectionId = "c456";
140+
141+
// Mock executeQuery to throw an exception
142+
Mockito.when(mockStmt.executeQuery(Mockito.anyString()))
143+
.thenThrow(new SQLException("Database error"));
144+
145+
// Wrap it using our proxy (which dynamically extracts "c456" as its connection ID!)
146+
Statement proxy = BigQueryJdbcContextProxy.wrap(mockStmt, Statement.class);
147+
assertNotNull(proxy);
148+
149+
// Call the proxy method. It will throw SQLException
150+
assertThrows(SQLException.class, () -> proxy.executeQuery("SELECT *"));
151+
152+
// Flush the handler to ensure logs are written to disk
153+
handler.flush();
154+
155+
// Verify that the exception log got registered and written directly to c456.log!
156+
Optional<Path> connLog = findLogFile("-c456.log");
157+
assertTrue(connLog.isPresent());
158+
159+
String content = new String(Files.readAllBytes(connLog.get()));
160+
assertTrue(content.contains("Database error"));
161+
assertTrue(content.contains("Exception occurred during executeQuery"));
162+
163+
} finally {
164+
// Cleanup
165+
rootLogger.removeHandler(handler);
166+
}
167+
}
168+
169+
/**
170+
* Verifies that when an exception is thrown from within an unproxied, raw ResultSet concrete
171+
* class while the thread-local connection ID context is completely missing (null), the internal
172+
* logAndCreateException() builder dynamically extracts the connection ID from its parent
173+
* statement, registers it on the executing thread, and writes the severe exception log record
174+
* directly to the connection-specific log file (e.g. "BQ-JDBC-timestamp-c789.log") instead of the
175+
* global log file.
176+
*/
177+
@Test
178+
public void testResultSetExceptionLogRouting() throws Exception {
179+
// Register the temp file handler to the root logger so ResultSet logs are captured
180+
java.util.logging.Logger rootLogger = BigQueryJdbcRootLogger.getRootLogger();
181+
rootLogger.addHandler(handler);
182+
183+
try {
184+
// Ensure thread context is completely missing (null) before call
185+
BigQueryJdbcMdc.clear();
186+
assertNull(BigQueryJdbcMdc.getConnectionId());
187+
188+
// Create a mock statement with connectionId = "c789"
189+
BigQueryStatement mockStmt = mock(BigQueryStatement.class);
190+
mockStmt.connectionId = "c789";
191+
192+
// Create a mock FieldList and schema for the ResultSet
193+
FieldList fields = FieldList.of(Field.of("col", StandardSQLTypeName.STRING));
194+
com.google.cloud.bigquery.Schema schema = com.google.cloud.bigquery.Schema.of(fields);
195+
196+
// Instantiate a real BigQueryJsonResultSet (which extends BigQueryBaseResultSet)
197+
// passing the mock statement carrying connectionId "c789"
198+
BigQueryJsonResultSet rs = BigQueryJsonResultSet.of(schema, 0, null, mockStmt, new Thread[0]);
199+
200+
// Calling findColumn(null) throws SQLException because column label is null
201+
assertThrows(SQLException.class, () -> rs.findColumn(null));
202+
203+
// Flush the handler to ensure logs are written to disk
204+
handler.flush();
205+
206+
// Verify that the ResultSet exception log got registered and written directly to c789.log!
207+
Optional<Path> connLog = findLogFile("-c789.log");
208+
assertTrue(connLog.isPresent());
209+
210+
String content = new String(Files.readAllBytes(connLog.get()));
211+
assertTrue(content.contains("Column label cannot be null"));
212+
213+
} finally {
214+
// Cleanup
215+
rootLogger.removeHandler(handler);
216+
}
217+
}
109218
}

0 commit comments

Comments
 (0)