String pattern matching best practice
Solution 1
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.
Solution 2
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)
Solution 3
Starting Scala 2.13
, it's possible to pattern match a String
s 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")
Nikita Volkov
Updated on July 24, 2020Comments
-
Nikita Volkov almost 4 years
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 } }
-
Nikita Volkov over 12 yearsThanks! 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 over 12 yearsThanks but isn't what you propose an abstraction over
match
? -
missingfaktor over 12 years@mojojojo: Not quite. The set of
case
expressions that followsmatch
constitutes aPartialFunction
.collectFirst
is a method that accepts aPartialFunction
, loops over the collection, and returns the first match found as wrapped inSome
. (returnsNone
if no match found.) -
jwvh almost 5 yearsCode-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.
-
Volty De Qua almost 5 yearsSorry, 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 almost 5 yearsSorry 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?
-
jwvh almost 5 years5-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.
-
niid over 3 yearsSo I take it that the matching of string interpolators also only works in >=2.13?