Reputation: 21811
For practice, I've gotten some straightforward JDBC stuff from like 100 lines down to this, but it won't typecheck. Any ideas? Better approaches?
def withResultSet[T](sql: String, f: ResultSet => T)(implicit info: ConnectionInfo): Try[T] = {
for {
conn <- Try(connect(info))
stmt <- Try(conn.createStatement()) orElse { case err: SQLException => {conn.close(); err} }
results <- Try(stmt.executeQuery(sql)) orElse { case err: SQLException => { conn.close(); stmt.close(); err }}
} yield f(results)
}
and my error is
missing parameter type for expanded function
The argument types of an anonymous function must be fully known. (SLS 8.5)
Expected type was: scala.util.Try[?]
stmt <- Try(conn.createStatement()) orElse { case err: SQLException => {conn.close(); err} }
^
Upvotes: 2
Views: 804
Reputation: 13008
You can split your function into different parts, one for each resource type, and use it together with a separate resource manager trait.
The generalized resource manager (can be used with files etc. as well):
trait ResourceManager {
def withResource[T <: {def close()}, R](resource: T)(code: (T) => R): R = {
try {
code(resource)
} finally {
import scala.language.reflectiveCalls
resource.close()
}
}
is mixed into something like this
class Db extends ResourceManager {
private def getConnection = ...
def withConnection[T](f: (Connection) => T): T = {
withResource(getConnection) {
conn =>
f(conn)
}
}
def withStatement[T](f: (Statement) => T): T = {
withConnection {
conn =>
withResource(conn.createStatement()) {
stmnt =>
f(stmnt)
}
}
}
def withResultSet[T](selectSqlCmd: String)(f: (ResultSet) => T): T = {
withStatement {
stmnt => {
withResource(stmnt.executeQuery(selectSqlCmd)) {
rs =>
f(rs)
}
}
}
}
}
which stacks the resources, and gives entry points at each level.
On top of this another level
def mapResultSet[T, C <: Iterable[T]](selectSqlCmd: String)
(f: (ResultSet) => T)
(implicit cbf: CanBuildFrom[Nothing, T, C]): C = {
withResultSet(selectSqlCmd) {
rs =>
val builder = cbf()
while (rs.next()) {
builder += f(rs)
}
builder.result()
}
}
Each method goes only one step further down, the for
comprehension is decomposed into separate functions, and the Try
does not interfere.
Use it like this:
val db = new Db()
val result = db.mapResultSet("select * from pg_user")(rs => rs.getString(1))
to read from the database in a single line.
Upvotes: 2
Reputation: 41646
I don't know that Try
is the right tool to dispose of resources once they are no longer needed. At least I cannot see an obvious way to do it. You may want to look at https://github.com/jsuereth/scala-arm. Then your code may look like this:
def withResultSet[T](sql: String, f: ResultSet => T)(
implicit info: ConnectionInfo): ManagedResource[T] = for {
conn <- managed(connect(info))
stmt <- managed(conn.createStatement())
results <- managed(stmt.executeQuery(sql))
} yield f(results)
You can keep using map or flatMap (or for comprehension) to work with possibly other resources and at the end you can get an Option
or an Either
out of it and at the same time close everything that needs to be closed.
Upvotes: 3
Reputation: 21811
recoverWith
is what i needed; you don't actually need to recover you can just rethrow.
def withResultSet[T](sql: String, f: ResultSet => T)(implicit info: ConnectionInfo): Try[T] = {
for {
conn <- Try(connect(info))
stmt <- Try(conn.createStatement()) recoverWith { case err => {conn.close(); Failure(err)} }
results <- Try(stmt.executeQuery(sql)) recoverWith { case err => { conn.close(); stmt.close(); Failure(err) }}
} yield f(results)
}
Upvotes: 1