Just using the basic JDBC interface to read some data using Scala. In F# (using System.Data.SqlClient namespace) we could do something like this to return an immutable list from the database.
let rs = cmd.ExecuteReader()
[while rs.Read() do yield rs.GetInt32(1)]
In Scala this proves more difficult, as far as I know there is no "while" comprehension like F#. Effectively I'd like to do something close to F# in Scala without having to use mutable vars - if only because they look ugly and add to Lines of Code.
Something like this seems to be commonplace in my Scala code right now:
var result = Seq.empty[Int]
val rs = stmt.executeQuery()
while (rs.next()) {
result = result :+ rs.getInt(1) }
I would create a custom subclass of Iterator
that wraps a query result. It's really easy; senia showed how.
But you could also
val rs = stmt.executeQuery
val it = Iterator.continually(if (rs.next()) Some(rs.getInt(1)) else None)
val result = it.takeWhile(_.isDefined).toList.flatten
You could use same way in scala, but I think it's ugly:
class Reader(var i: Int){
def read = { i-=1; i > 0 }
def getInt32 = i
}
val r = new Reader(10)
Stream.from(0).takeWhile{ _ => r.read}.map{ _ => r.getInt32}.toList
// List(9, 8, 7, 6, 5, 4, 3, 2, 1)
Idiomatic scala way is to convert your Reader
to an Iterator
:
implicit class ReaderIterator(r: Reader) extends Iterator[Int] {
def hasNext = r.read
def next = r.getInt32
}
scala> new Reader(10).toList
res0: List[Int] = List(9, 8, 7, 6, 5, 4, 3, 2, 1)
But if you are really missing this syntax you could add it:
import scala.collection.immutable.VectorBuilder
class FWhile(c: => Boolean){
def apply[T](e: => T): Seq[T] = {
val b = new VectorBuilder[T]
while (c) b += e
b.result
}
}
object FWhile{
def apply(c: => Boolean) = new FWhile(c)
}
scala> FWhile(r.read){r.getInt32}
res0: Seq[Int] = Vector(9, 8, 7, 6, 5, 4, 3, 2, 1)
You could use an implicit class together with an implicit CanBuildFrom
. This does use a mutable builder, but not at the caller's side:
object MyResultSetContainer {
implicit class MyResultSet(rs: ResultSet) {
def map[T, C <: Iterable[T]](f: (ResultSet) => T)
(implicit cbf: CanBuildFrom[Nothing, T, C]): C = {
val builder = cbf()
while (rs.next()) {
builder += f(rs)
}
builder.result()
}
}
}
to be used like this:
import MyResultSetContainer._
val rs = stmnt.executeQuery("select * from pg_user")
val names = for (row <- rs) yield (row.getString(1))
println(names)
rs.close()
The for comprehension uses map
under the hood, so if you prefer map
directly:
val names = rs.map(row => row.getString(1))
which produces a sequence. Thanks to CanBuildFrom
you can produce other collections as well by providing a type explicitly:
val names: List[String] = rs.map(row => row.getString(1))
How does CanBuildFrom
work? The Scala compiler looks at the types involved in this expression: There is the resulting type, and the type returned by the function called by map. Based on this information, the Scala compiler provides a factory implicitly which can be used to create a suitable builder. So you need only one method to produce different types of collections.
If you want to return multiple values, just return a tuple:
val columns = rs.map(row => (row.getInt(2), row.getString(1)))
and the tuple can be used to create a Map
directly:
val keyNamesMap: Map[Int, String] =
rs.map(row => (row.getInt(2), row.getString(1)))
This is based on the idea that a result set is a list of rows, and so the map
function should be available on top of it. The implicit class is used to add the map
method to the underlying result set implicitly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With