Skip to content

Commit b7bd7c5

Browse files
committed
Fix remaining JDBC lifecycle validation gaps
1 parent 6f72bcb commit b7bd7c5

4 files changed

Lines changed: 119 additions & 12 deletions

File tree

‎flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightConnection.java‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,12 +313,16 @@ public PreparedStatement prepareStatement(
313313
final int resultSetHoldability)
314314
throws SQLException {
315315
checkOpen();
316-
return ArrowFlightPreparedStatement.builder(this)
317-
.withQuery(sql)
318-
.withGeneratedHandle()
319-
.withResultSetType(resultSetType)
320-
.withResultSetConcurrency(resultSetConcurrency)
321-
.withResultSetHoldability(resultSetHoldability)
322-
.build();
316+
try {
317+
return ArrowFlightPreparedStatement.builder(this)
318+
.withQuery(sql)
319+
.withGeneratedHandle()
320+
.withResultSetType(resultSetType)
321+
.withResultSetConcurrency(resultSetConcurrency)
322+
.withResultSetHoldability(resultSetHoldability)
323+
.build();
324+
} catch (final RuntimeException e) {
325+
throw HELPER.createException("while preparing SQL:" + sql, e);
326+
}
323327
}
324328
}

‎flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightMetaImpl.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Collections;
2424
import java.util.List;
2525
import org.apache.arrow.driver.jdbc.utils.ConvertUtils;
26+
import org.apache.arrow.util.Preconditions;
2627
import org.apache.arrow.vector.types.pojo.Schema;
2728
import org.apache.calcite.avatica.AvaticaConnection;
2829
import org.apache.calcite.avatica.AvaticaParameter;
@@ -98,6 +99,7 @@ public Frame fetch(
9899
@Override
99100
public StatementHandle prepare(
100101
final ConnectionHandle connectionHandle, final String query, final long maxRowCount) {
102+
validateConnectionId(connectionHandle.id);
101103
try {
102104
// This is the Avatica entry point used by Connection.prepareStatement(String).
103105
ArrowFlightPreparedStatement stmt =
@@ -192,6 +194,7 @@ void setDefaultConnectionProperties() {
192194
}
193195

194196
private AvaticaStatement getStatement(final StatementHandle statementHandle) {
197+
validateConnectionId(statementHandle.connectionId);
195198
final AvaticaStatement statement = connection.statementMap.get(statementHandle.id);
196199
if (statement instanceof ArrowFlightStatement
197200
|| statement instanceof ArrowFlightPreparedStatement) {
@@ -209,6 +212,11 @@ private ArrowFlightPreparedStatement getPreparedStatement(final StatementHandle
209212
"Statement operation is not supported for handle: " + statementHandle);
210213
}
211214

215+
private void validateConnectionId(final String connectionId) {
216+
Preconditions.checkArgument(
217+
connection.id.equals(connectionId), "Connection IDs are not consistent");
218+
}
219+
212220
static Signature buildDefaultSignature() {
213221
return buildSignature(null, StatementType.SELECT);
214222
}

‎flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowFlightPreparedStatement.java‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,32 @@ Schema getDataSetSchema() {
102102

103103
@Override
104104
protected void close_() {
105+
RuntimeException closeFailure = null;
105106
try {
106107
super.close_();
107-
} finally {
108-
((ArrowFlightConnection) connection).unregisterStatementOwner(this);
108+
} catch (final RuntimeException e) {
109+
closeFailure = e;
110+
}
111+
try {
112+
closePreparedResources();
113+
} catch (final RuntimeException e) {
114+
if (closeFailure == null) {
115+
closeFailure = e;
116+
} else {
117+
closeFailure.addSuppressed(e);
118+
}
119+
}
120+
((ArrowFlightConnection) connection).unregisterStatementOwner(this);
121+
if (closeFailure != null) {
122+
throw closeFailure;
109123
}
110124
}
111125

112126
void closePreparedResources() {
113-
if (preparedStatement != null) {
114-
preparedStatement.close();
115-
preparedStatement = null;
127+
final ArrowFlightSqlClientHandler.PreparedStatement statement = preparedStatement;
128+
preparedStatement = null;
129+
if (statement != null) {
130+
statement.close();
116131
}
117132
}
118133

‎flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/ArrowFlightPreparedStatementTest.java‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import static org.junit.jupiter.api.Assertions.assertSame;
2727
import static org.junit.jupiter.api.Assertions.assertThrows;
2828
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
import static org.mockito.Mockito.doThrow;
30+
import static org.mockito.Mockito.mock;
2931

3032
import java.nio.charset.StandardCharsets;
3133
import java.sql.Connection;
@@ -52,6 +54,8 @@
5254
import org.apache.arrow.vector.types.pojo.FieldType;
5355
import org.apache.arrow.vector.types.pojo.Schema;
5456
import org.apache.arrow.vector.util.Text;
57+
import org.apache.calcite.avatica.AvaticaResultSet;
58+
import org.apache.calcite.avatica.AvaticaStatement;
5559
import org.junit.jupiter.api.AfterAll;
5660
import org.junit.jupiter.api.BeforeAll;
5761
import org.junit.jupiter.api.BeforeEach;
@@ -177,6 +181,82 @@ public void testPrepareFailureIsReportedAsSQLException() {
177181
assertThat(exception.getCause(), instanceOf(FlightRuntimeException.class));
178182
}
179183

184+
@Test
185+
public void testNullQueryIsReportedAsSQLException() {
186+
final SQLException exception =
187+
assertThrows(SQLException.class, () -> connection.prepareStatement(null));
188+
189+
assertThat(exception.getCause(), instanceOf(NullPointerException.class));
190+
}
191+
192+
@Test
193+
public void testCloseReleasesPreparedResourcesWhenResultSetCloseFails() throws Exception {
194+
try (final ArrowFlightConnection localConnection =
195+
(ArrowFlightConnection) FLIGHT_SERVER_TEST_EXTENSION.getConnection(false)) {
196+
final ArrowFlightPreparedStatement preparedStatement =
197+
(ArrowFlightPreparedStatement)
198+
localConnection.prepareStatement(CoreMockedSqlProducers.LEGACY_REGULAR_SQL_CMD);
199+
final AvaticaResultSet resultSet = mock(AvaticaResultSet.class);
200+
final RuntimeException resultSetCloseFailure =
201+
new RuntimeException("Result set close failed");
202+
doThrow(resultSetCloseFailure).when(resultSet).close();
203+
final java.lang.reflect.Field openResultSetField =
204+
AvaticaStatement.class.getDeclaredField("openResultSet");
205+
openResultSetField.setAccessible(true);
206+
openResultSetField.set(preparedStatement, resultSet);
207+
208+
final SQLException exception = assertThrows(SQLException.class, preparedStatement::close);
209+
210+
assertSame(resultSetCloseFailure, exception.getCause());
211+
assertTrue(preparedStatement.isClosed());
212+
assertEquals(
213+
1,
214+
PRODUCER
215+
.getActionTypeCounter()
216+
.getOrDefault(FlightSqlUtils.FLIGHT_SQL_CLOSE_PREPARED_STATEMENT.getType(), 0));
217+
}
218+
}
219+
220+
@Test
221+
public void testMetaRejectsForeignStatementHandle() throws SQLException {
222+
try (final ArrowFlightConnection firstConnection =
223+
(ArrowFlightConnection) FLIGHT_SERVER_TEST_EXTENSION.getConnection(false);
224+
final ArrowFlightConnection secondConnection =
225+
(ArrowFlightConnection) FLIGHT_SERVER_TEST_EXTENSION.getConnection(false);
226+
final PreparedStatement firstStatement =
227+
firstConnection.prepareStatement(CoreMockedSqlProducers.LEGACY_REGULAR_SQL_CMD);
228+
final PreparedStatement secondStatement =
229+
secondConnection.prepareStatement(
230+
CoreMockedSqlProducers.UUID_PREPARED_SELECT_SQL_CMD)) {
231+
final ArrowFlightPreparedStatement firstPreparedStatement =
232+
(ArrowFlightPreparedStatement) firstStatement;
233+
final ArrowFlightPreparedStatement secondPreparedStatement =
234+
(ArrowFlightPreparedStatement) secondStatement;
235+
assertEquals(firstPreparedStatement.handle.id, secondPreparedStatement.handle.id);
236+
237+
assertThrows(
238+
IllegalArgumentException.class,
239+
() -> firstConnection.getMeta().closeStatement(secondPreparedStatement.handle));
240+
assertFalse(firstPreparedStatement.isClosed());
241+
}
242+
}
243+
244+
@Test
245+
public void testMetaRejectsForeignConnectionHandle() throws SQLException {
246+
try (final ArrowFlightConnection firstConnection =
247+
(ArrowFlightConnection) FLIGHT_SERVER_TEST_EXTENSION.getConnection(false);
248+
final ArrowFlightConnection secondConnection =
249+
(ArrowFlightConnection) FLIGHT_SERVER_TEST_EXTENSION.getConnection(false)) {
250+
assertThrows(
251+
IllegalArgumentException.class,
252+
() ->
253+
firstConnection
254+
.getMeta()
255+
.prepare(
256+
secondConnection.handle, CoreMockedSqlProducers.LEGACY_REGULAR_SQL_CMD, -1));
257+
}
258+
}
259+
180260
@Test
181261
public void testQueryWithParameterBinding() throws SQLException {
182262
final String query = "Fake query with parameters";

0 commit comments

Comments
 (0)