8

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) }
imz -- Ivan Zakharyaschev
  • 4,921
  • 6
  • 53
  • 104
Richard Todd
  • 2,406
  • 5
  • 32
  • 40
  • 3
    Actually in idiomatic scala it would look like `val rs = stmt.executeQuery(); val result = for(r <- rs) yield r.getInt(1)` (just a pseudocode, if you will) – om-nom-nom Jun 24 '13 at 13:29
  • You may want to take a look at this post: http://stackoverflow.com/questions/2102662/scala-exposing-a-jdbc-resultset-through-a-generator-iterable/15950556#15950556 – Björn Jacobs Jun 24 '13 at 14:19
  • 2
    if you're looking for LINQ-like queries in scala, take a look at [Slick](http://slick.typesafe.com/) – pagoda_5b Jun 24 '13 at 14:28
  • om-nom-nom: actually you cant do that since rs is a recordset and can't be iterated like that. – Richard Todd Jun 25 '13 at 19:36

3 Answers3

9

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
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
7

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)
senia
  • 37,745
  • 4
  • 88
  • 129
5

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.

Beryllium
  • 12,808
  • 10
  • 56
  • 86