Skip to content

Commit

Permalink
Introduce ResultSetIterable
Browse files Browse the repository at this point in the history
Safely useable with foreach loop
  • Loading branch information
gwenn committed May 12, 2024
1 parent efd5c53 commit 6c24f1d
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 6 deletions.
9 changes: 3 additions & 6 deletions src/main/java/org/sqlite/Stmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static org.sqlite.ColTypes.SQLITE_NULL;
import static org.sqlite.SQLite.*;
import static org.sqlite.driver.Guard.sneakyThrow;

public class Stmt implements AutoCloseable, Row {
final Conn c;
Expand Down Expand Up @@ -164,7 +165,7 @@ public boolean hasNext() {
return false;
}
} catch (SQLiteException e) {
throw new IllegalStateException(e);
return sneakyThrow(e);
}
}
@Override
Expand All @@ -176,13 +177,9 @@ public T next() {
try {
return mapper.map(Stmt.this);
} catch (StmtException e) {
throw new IllegalStateException(e);
return sneakyThrow(e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
private enum State {
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/org/sqlite/driver/Guard.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.sqlite.driver;

import java.sql.SQLException;

public interface Guard extends AutoCloseable {
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
static <E extends Throwable, T> T sneakyThrow(SQLException e) throws E {
throw (E) e;
}

void close() throws SQLException;

class SneakyGuard<T> implements AutoCloseable, Runnable {
private final Guard guard;
public SneakyGuard(Guard guard) {
this.guard = guard;
}
@Override
public void close() {
try {
guard.close();
} catch (SQLException e) {
sneakyThrow(e);
}
}
@Override
public void run() {
close();
}
}
}
11 changes: 11 additions & 0 deletions src/main/java/org/sqlite/driver/Mapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sqlite.driver;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
* Row mapper used by {@link ResultSetIterable#iterator()}
*/
public interface Mapper<T> {
T map(ResultSet rs) throws SQLException;
}
27 changes: 27 additions & 0 deletions src/main/java/org/sqlite/driver/Query.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.sqlite.driver;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
* {@link ResultSetIterable#iterator()}
*/
public interface Query extends Guard {
static Query from(PreparedStatement stmt) {
return new Query() {
@Override
public ResultSet executeQuery() throws SQLException {
return stmt.executeQuery();
}
@Override
public void close() throws SQLException {
stmt.close();
}
};
}
/**
* @return {@link PreparedStatement#executeQuery()}
*/
ResultSet executeQuery() throws SQLException;
}
110 changes: 110 additions & 0 deletions src/main/java/org/sqlite/driver/ResultSetIterable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.sqlite.driver;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static org.sqlite.driver.Guard.sneakyThrow;

/**
* <pre>{@code
* try (Connection conn = DriverManager.getConnection(JDBC.MEMORY);
* PreparedStatement stmt = conn.prepareStatement("SELECT 1");
* ResultSetIterable<String> rsi = new ResultSetIterable<String>(Query.from(stmt), rs -> rs.getString(1))) {
* for (String s : rsi) {
* System.out.println("s = " + s);
* }
* }
* }</pre>
*/
public class ResultSetIterable<T> implements Iterable<T>, Guard {
private final Query query;
private final Mapper<T> mapper;
private ResultSet rs;

protected ResultSetIterable(Query query, Mapper<T> mapper) {
this.query = query;
this.mapper = mapper;
}

@Override
public void close() throws SQLException {
if (rs != null) {
rs.close();
}
query.close();
}

@Override
public Iterator<T> iterator() {
try {
if (rs != null) {
rs.close(); // previous result set
}
rs = query.executeQuery();
} catch (SQLException e) {
return sneakyThrow(e);
}
return new Iterator<T>() {
private State state = State.NOT_READY;
@Override
public boolean hasNext() {
if (State.FAILED == state) {
throw new IllegalStateException();
}
if (State.DONE == state) {
return false;
} else if (State.READY == state) {
return true;
}
state = State.FAILED;
try {
if (rs.next()) {
state = State.READY;
return true;
} else {
rs.close(); // close as soon as possible
state = State.DONE;
return false;
}
} catch (SQLException e) {
return sneakyThrow(e);
}
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
state = State.NOT_READY;
try {
return mapper.map(rs);
} catch (SQLException e) {
return sneakyThrow(e);
}
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
try (SneakyGuard ignored = rs != null ? new SneakyGuard(rs::close) : null) {
Iterator.super.forEachRemaining(action);
}
}
};
}

/*@Override
public Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED); // ORDERED ?
}*/
public Stream<T> stream() {
return StreamSupport.stream(spliterator(), false).onClose(new SneakyGuard(this));
}

private enum State {
READY, NOT_READY, DONE, FAILED,
}
}

0 comments on commit 6c24f1d

Please sign in to comment.