33

Following is the code that doesn't work but it describes what I want to do.

Could you please recommend the best approach to this problem?

def resolveDriver(url: String) = {
  url match {
    case url.startsWith("jdbc:mysql:") => "com.mysql.jdbc.Driver"
    case url.startsWith("jdbc:postgresql:") => "org.postgresql.Driver"
    case url.startsWith("jdbc:h2:") => "org.h2.Driver"
    case url.startsWith("jdbc:hsqldb:") => "org.hsqldb.jdbcDriver"
    case _ => throw new IllegalArgumentException
  }
}
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • 2
    See also [this question](http://stackoverflow.com/q/7586605/53013) for another way to solve this problem, if the matched part happens to be the whole protocol. – Daniel C. Sobral Oct 09 '11 at 00:46

4 Answers4

49

In terms of syntax, you can modify just a tiny bit you case statements:

case url if url.startsWith("jdbc:mysql:") => "com.mysql.jdbc.Driver"

This simply binds the value url to the pattern expression (which is also url) and adds a guard expression with a test. That should make the code compile.

To make it a little bit more scala-like, you can return an Option[String] (I removed a couple clause since it's just for illustration):

def resolveDriver(url: String) = url match {
  case u if u.startsWith("jdbc:mysql:") => Some("com.mysql.jdbc.Driver")
  case u if u.startsWith("jdbc:postgresql:") => Some("org.postgresql.Driver")
  case _ => None
}

That is unless you want to manage exceptions.

huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Thanks! That's exactly what I was looking for! I'm glad I asked the question 'cuz I was already preparing myself to create a case class for that, which smelled like an overcomplication. Also I thank you for correcting me on the Exception throwing. – Nikita Volkov Oct 08 '11 at 19:21
12

Starting Scala 2.13, it's possible to pattern match a Strings by unapplying a string interpolator:

val s"jdbc:$dialect:$rest" = "jdbc:mysql:whatever"
// dialect: String = "mysql"
// rest: String = "whatever"

Then in our case, it's just a matter of mapping the extracted value (the sql dialect) to the appropriate driver using a Map:

val drivers = Map(
  "postgresql" -> "org.postgresql.Driver",
  "mysql"      -> "com.mysql.jdbc.Driver",
  "h2"         -> "org.h2.Driver"
)
val driver = drivers(dialect)
// driver: String = "com.mysql.jdbc.Driver"

If you are expecting malformed inputs, you can also use a match statement:

"jdbc:postgresql:something" match {
  case s"jdbc:$dialect:$rest" => Some(dialect)
  case _                      => None
}
// Option[String] = Some("postgresql")
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
  • 2
    So I take it that the matching of string interpolators also only works in >=2.13? – niid Nov 24 '20 at 14:21
11

Here is an alternate way. Store all the mappings in a map and then use collectFirst method to find the match. Type signature of collectFirst is:

def TraversableOnce[A].collectFirst[B](pf: PartialFunction[A, B]): Option[B]

Usage:

scala> val urlMappings = Map("jdbc:mysql:" -> "com.mysql.jdbc.Driver", "jdbc:postgresql:" -> "org.postgresql.Driver")
urlMappings: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(jdbc:mysql: -> com.mysql.jdbc.Drive
r, jdbc:postgresql: -> org.postgresql.Driver)

scala> val url = "jdbc:mysql:somestuff"
url: java.lang.String = jdbc:mysql:somestuff

scala> urlMappings collectFirst { case(k, v) if url startsWith k => v }
res1: Option[java.lang.String] = Some(com.mysql.jdbc.Driver)
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • Thanks but isn't what you propose an abstraction over `match`? – Nikita Volkov Oct 08 '11 at 19:21
  • @mojojojo: Not quite. The set of `case` expressions that follows `match` constitutes a `PartialFunction`. `collectFirst` is a method that accepts a `PartialFunction`, loops over the collection, and returns the first match found as wrapped in `Some`. (returns `None` if no match found.) – missingfaktor Oct 08 '11 at 19:28
0
import PartialFunction._  // condOpt

val drivers = Map( 
  "mysql" -> "mysql driver",
  "h2" -> "h2 driver" 
  // ... 
)
val pattern = "^jdbc:(\\w+):.*".r

def resolveDriver(url: String) = condOpt(url) { 
  case pattern(dbms) => drivers.get(dbms) 
}.flatten.getOrElse(throw new IllegalArgumentException)
Volty De Qua
  • 199
  • 6
  • 2
    Code-only answers aren't as useful as code-with-commentary, especially with questions this old (over 7 year) it is useful to point out how your answer differs from all the previous answers. – jwvh Jul 23 '19 at 18:49
  • Sorry, but if others are supposed to (also) read the previous answers, then the code I posted is self-evident - map driven on pertinent parts, with full pattern match capturing dbms part (and eventually else), then partial function that saves us the stating of None. And all this taking into account the the initial question and code. I do it this way. Whether it's better or not, whether it differs enough from others', is just an opinion. Thanks. – Volty De Qua Jul 23 '19 at 23:46
  • Sorry to flood, but I do not understand your point about the age of this question. How / why the age matters? Fresh questions need less elaboration? Aren't this kind of questions ageless? – Volty De Qua Jul 23 '19 at 23:57
  • 1
    5-It's not uncommon (far too common) for newcomers to encounter an old question and answer it without reading the previous answers. Commentary accompanying your code can make it clear that you're not in that category. – jwvh Jul 24 '19 at 04:05