-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Safely useable with foreach loop
- Loading branch information
Showing
5 changed files
with
182 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |