Skip to content

Commit

Permalink
refactor: Make sure result set can only be consumed once, add test fo…
Browse files Browse the repository at this point in the history
…r empty result set meta data (#559)

Closes #419.
  • Loading branch information
michael-simons authored Mar 11, 2024
1 parent 53f6c61 commit e93d810
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
Expand Down Expand Up @@ -233,6 +235,33 @@ void executeShouldAutomaticallyTranslate() throws SQLException {
}
}

// GH-419
@Test
void resultSetMustNotThrowWhenEmpty() throws SQLException {
try (var connection = getConnection();
var stmt = connection.prepareStatement("MATCH(p:Person) WHERE p.date >= ? AND p.date < ? RETURN p")) {

var ts = Timestamp.from(Instant.now());
stmt.setTimestamp(1, ts);
stmt.setTimestamp(2, ts);
stmt.execute();
var resultSet = stmt.getResultSet();
var resultSetMetaData = resultSet.getMetaData();
int columnCount = resultSetMetaData.getColumnCount();
assertThat(columnCount).isOne();
for (int c = 1; c < columnCount + 1; c++) {
var label = resultSetMetaData.getColumnLabel(c);
assertThat(label).isEqualTo("p");
var type = resultSetMetaData.getColumnType(c);
assertThat(type).isEqualTo(Types.NULL);
var typeName = resultSetMetaData.getColumnTypeName(c);
assertThat(typeName).isEmpty();
}
resultSet.close();

}
}

@Nested
class SymmetricTypeConversionInPreparedStatementAndResultSet {

Expand Down
8 changes: 8 additions & 0 deletions neo4j-jdbc/src/main/java/org/neo4j/jdbc/StatementImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -84,6 +85,8 @@ non-sealed class StatementImpl implements Neo4jStatement {

private final Warnings warnings;

private final AtomicBoolean resultSetAcquired = new AtomicBoolean(false);

StatementImpl(Connection connection, Neo4jTransactionSupplier transactionSupplier,
UnaryOperator<String> sqlProcessor, Warnings localWarnings) {
this.connection = Objects.requireNonNull(connection);
Expand Down Expand Up @@ -121,6 +124,7 @@ protected final ResultSet executeQuery0(String sql, boolean applyProcessor, Map<
var runAndPull = transaction.runAndPull(sql, getParameters(parameters), fetchSize, this.queryTimeout);
this.resultSet = new ResultSetImpl(this, transaction, runAndPull.runResponse(), runAndPull.pullResponse(),
this.fetchSize, this.maxRows, this.maxFieldSize);
this.resultSetAcquired.set(false);
return this.resultSet;
}

Expand Down Expand Up @@ -287,6 +291,9 @@ else if (entry.getValue() instanceof InputStream inputStream) {
@Override
public ResultSet getResultSet() throws SQLException {
assertIsOpen();
if (!this.resultSetAcquired.compareAndSet(false, true)) {
throw new SQLException("Result set has already been acquired");
}
return (this.multipleResultsApi && this.updateCount == -1) ? this.resultSet : null;
}

Expand Down Expand Up @@ -472,6 +479,7 @@ private void closeResultSet() throws SQLException {
if (this.resultSet != null) {
this.resultSet.close();
this.resultSet = null;
this.resultSetAcquired.set(false);
}
}

Expand Down

0 comments on commit e93d810

Please sign in to comment.