11

I'm currently experimenting with Scala and looking for best practices. I found myself having two opposite approaches to solving a single problem. I'd like to know which is better and why, which is more conventional, and if maybe you know of some other better approaches. The second one looks prettier to me.

1. Enumeration-based solution

import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver

object DBType extends Enumeration {
  val MySql, PostgreSql, H2 = Value

  def fromUrl(url: String) = {
    url match {
      case u if u.startsWith("jdbc:mysql:") => Some(MySql)
      case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql)
      case u if u.startsWith("jdbc:h2:") => Some(H2)
      case _ => None
    }
  }
}

case class DBType(typ: DBType) {
  lazy val driver: Driver = {
    val name = typ match {
      case DBType.MySql => "com.mysql.jdbc.Driver"
      case DBType.PostgreSql => "org.postgresql.Driver"
      case DBType.H2 => "org.h2.Driver"
    }
    Class.forName(name).newInstance().asInstanceOf[Driver]
  }
  lazy val adapter: DatabaseAdapter = {
    typ match {
      case DBType.MySql => new MySQLAdapter
      case DBType.PostgreSql => new PostgreSqlAdapter
      case DBType.H2 => new H2Adapter
    }
  }
}

2. Singleton-based solution

import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver

trait DBType {
  def driver: Driver
  def adapter: DatabaseAdapter
}

object DBType {
  object MySql extends DBType {
    lazy val driver = Class.forName("com.mysql.jdbc.Driver").newInstance().asInstanceOf[Driver]
    lazy val adapter = new MySQLAdapter
  }

  object PostgreSql extends DBType {
    lazy val driver = Class.forName("org.postgresql.Driver").newInstance().asInstanceOf[Driver]
    lazy val adapter = new PostgreSqlAdapter
  }

  object H2 extends DBType {
    lazy val driver = Class.forName("org.h2.Driver").newInstance().asInstanceOf[Driver]
    lazy val adapter = new H2Adapter
  }

  def fromUrl(url: String) = {
    url match {
      case u if u.startsWith("jdbc:mysql:") => Some(MySql)
      case u if u.startsWith("jdbc:postgresql:") => Some(PostgreSql)
      case u if u.startsWith("jdbc:h2:") => Some(H2)
      case _ => None
    }
  }
}
Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169

3 Answers3

12

If you declare a sealed trait DBType, you can pattern match on it with exhaustiveness checking (ie, Scala will tell you if you forget one case).

Anyway, I dislike Scala's Enumeration, and I'm hardly alone in that. I never use it, and if there's something for which enumeration is really the cleanest solution, it is better to just write it in Java, using Java's enumeration.

Noel
  • 2,061
  • 4
  • 31
  • 47
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • I fully agree. Scala enumerations are absolutely useless. They only provide auto-generation of sequential values, which I doubt someone needs at all. Contrary, there is no good way to look up a value by string id (reflection is employed underneath) and there is no legal way to resolve Enumeration from Enumeration#Value. – Jiří Vypědřík May 10 '13 at 15:22
10

For this particular case you don't really need classes for each database type; it's just data. Unless the real case is dramatically more complex, I would use a map and string parsing based solution to minimize the amount of code duplication:

case class DBRecord(url: String, driver: String, adapter: () => DatabaseAdapter) {}

class DBType(record: DBRecord) {
  lazy val driver = Class.forName(record.driver).newInstance().asInstanceOf[Driver]
  lazy val adapter = record.adapter()
}

object DBType {
  val knownDB = List(
    DBRecord("mysql", "com.mysql.jdbc.Driver", () => new MySQLAdapter),
    DBRecord("postgresql", "org.postgresql.Driver", () => new PostgreSqlAdapter),
    DBRecord("h2", "org.h2.Driver", () => new H2Adapter)
  )

  val urlLookup = knownDB.map(rec => rec.url -> rec).toMap

  def fromURL(url: String) = {
    val parts = url.split(':')
    if (parts.length < 3 || parts(0) != "jdbc") None
    else urlLookup.get(parts(1)).map(rec => new DBType(rec))
  }
}
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
4

I'd go for the singleton variant, since it allows clearer subclassing.

Also you might need to do db-specific things/overrides, since some queries/subqueries/operators might be different.

But i'd try something like this:

import org.squeryl.internals.DatabaseAdapter
import org.squeryl.adapters.{H2Adapter, MySQLAdapter, PostgreSqlAdapter}
import java.sql.Driver

abstract class DBType(jdbcDriver: String) {
  lazy val driver = Class.forName(jdbcDriver).newInstance().asInstanceOf[Driver]
  def adapter: DatabaseAdapter
}


object DBType {
  object MySql extends DBType("com.mysql.jdbc.Driver") {
    lazy val adapter = new MySQLAdapter
  }

  object PostgreSql extends DBType("org.postgresql.Driver") {
    lazy val adapter = new PostgreSqlAdapter
  }

  object H2 extends DBType("org.h2.Driver") {
    lazy val adapter = new H2Adapter
  }

  def fromUrl(url: String) = {
    url match {
      case _ if url.startsWith("jdbc:mysql:") => Some(MySql(url))
      case _ if url.startsWith("jdbc:postgresql:") => Some(PostgreSql(url))
      case _ if url.startsWith("jdbc:h2:") => Some(H2(url))
      case _ => None
    }

}

if this helped, please consider to +1 this :)

Franz Bettag
  • 437
  • 2
  • 8
  • Traits cannot have parameters. Was that supposed to be an abstract class? – Rex Kerr Oct 09 '11 at 03:46
  • yes sorry, i just copied your code and forgot about the trait there. – Franz Bettag Oct 09 '11 at 03:49
  • Sorry! 6AM here, haven't slept ;) Just trying to help. – Franz Bettag Oct 09 '11 at 04:00
  • 3
    I don't think that this service is about begging for scores. Have some dignity man. What do I have to mark answered here? The question was **which is better and why, which is more conventional, and if maybe you know of some other better approaches** - and how copy-pasting my code with primitive corrections has anything to do with that? – Nikita Volkov Oct 09 '11 at 13:16
  • You wanted an opinion with a reason, i gave you both with a minor code improvement. sorry, won't happen again. Also, if people wouldn't constantly forget to mark their questions answered, someone might be able to use the unanswered tab correctly. – Franz Bettag Oct 09 '11 at 17:33