Insert or get in Slick 3 - scala

I was trying to insert if not exists, and get the row if it does.
I come up with this:
def saveOrGet(u: User) = (for {
userGet <- get(u.name).map(r => (r.id, r.active)).result.headOption
id <- save(u) if userGet.isEmpty
} yield {
userGet match {
case Some((id, active)) => (User(Some(id), u.name, active), false)
case None => (User(Some(id), u.name, u.active), true)
}
}).transactionally
private def get(name: String) = users.filter(_.name === name).take(1)
private def save(u: User) = users returning users.map(_.id) += u
EDIT
After making some modifications, now I get:
java.util.NoSuchElementException: Action.withFilter failed
Regards

Try using case _, because it seems that it's possible to have something empty rather than None
userGet match {
case Some((id, active)) => (User(Some(id), u.name, active), false)
case _ => (User(Some(id), u.name, u.active), true)
}

Related

Scala - How to return Option[String] instead of Any with pattern matching?

I wanted to get Sale objects from HBase concatenated with their HBase ids (a string representation of ImmutableBytesWritable) as Option[String].
First I've implemented processSales method so that it just returned all sales + hBase ids as shown below:
private def processSales (result: Result, hBaseId: String): Option[String] = {
val triedSale: Try[Sale] = myThriftCodec.invert(result.getValue("binary", "object"))
triedSale match {
case Success(sale) => Some(hBaseId + sale)
case _ => None
}
}
Now I want to return only those concatenated hBaseIds + Sales where Sales have metric_1 == null
So I tried the following:
private def processSales (result: Result, hBaseId: String): Any = {
val triedSale: Try[Sale] = myThriftCodec.invert(result.getValue("binary", "object"))
triedSale match {
case Success(sale) => Some(hBaseId + sale)
case _ => None
}
triedSale match {
case someSale => if (someSale.get.metric_1 = null) someSale.map(sale => hBaseId + sale)
}
}
But it seems that I'm missing something here and the method returns Any even if I wrap this like this Option(hBaseId + sale).
What should I fix in my logic in order to return Option[String] with sales having metric_1 == null ?
UPD: Downvoting without pointing out the problems with my question doesn't make sense. It just totally demotivates seeking new knowledge.
You are missing the second option of the match case in your else, so it's returning Unit when the metric is not null, so Unit in one case, and Option(String) in another, the compiler guess that you want Any as return type
What do you want to return when the metric_1 is not null? In this example you return the exact same input:
triedSale match {
case someSale => if (someSale.get.metric_1 = null) someSale.map(s => hBaseId + s) else someSale
}
Or in a more elegant way you can do:
triedSale match {
case Success(metric_1) if metric_1 = null => Some(hBaseId + metric_1)
case Success(metric_1) if metric_1 != null => triedSale
case _ => None
}
EDIT
As per the comments, you only want to return something when the metric_1 is null so here is the best solution as for my understanding:
Also why are you pattern matching the same variable twice?
triedSale match {
case someSale => if (someSale.get.metric_1 = null) someSale.map(s => hBaseId + s) else None
}
Or something like this:
triedSale match {
case Success(metric_1) if metric_1 = null => Some(hBaseId + metric_1)
case _ => None
}
Isn't just as simple as below?
myThriftCodec.invert(result.getValue("binary", "object"))
.toOption
.filter(_.metric_1 == null)
.map(hBaseId+_)

Create map based on condition from Future of List in Scala

I have method with param type Future[List[MyRes]]. MyRes has two option fields id and name. Now I want to create map of id and name if both present. I am able to create map with default value as follow but I don't want to have default value just skip the entry with null value on either.
def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
myRes.map (
_.map(
o =>
(o.id match {
case Some(id) => id.toLong
case _ => 0L
}) ->
(o.name match {
case Some(name) => name
case _ => ""
})
).toMap)
Any suggestion?
You are looking for collect :)
myRes.map {
_.iterator
.map { r => r.id -> r.name }
.collect { case(Some(id), Some(name) => id -> name }
.toMap
}
If your MyRes thingy is a case class, then you don't need the first .map:
myRes.map {
_.collect { case MyRes(Some(id), Some(name)) => id -> name }
.toMap
}
collect is like .map, but it takes a PartialFunction, and skips over elements on which it is not defined. It is kinda like your match statement but without the defaults.
Update:
If I am reading your comment correctly, and you want to log a message when either field is a None, collect won't help with that, but you can do flatMap:
myRes.map {
_.flatMap {
case MyRes(Some(id), Some(name)) => Some(id -> name)
case x => loger.warn(s"Missing fields in $x."); None
}
.toMap
}
Try this:
def myMethod(myRes: Future[List[MyRes]]): Future[Map[Long, String]] = {
myRes.map (
_.flatMap(o =>
(for (id <- o.id; name <- o.name) yield (id.toLong -> name)).toList
).toMap
)
}
The trick is flattening List[Option[(Long,String)]] by using flatMap and converting the Option to a List.

Tuple seen as Product, compiler rejects reference to element

Constructing phoneVector:
val phoneVector = (
for (i <- 1 until 20) yield {
val p = killNS(r.get("Phone %d - Value" format(i)))
val t = killNS(r.get("Phone %d - Type" format(i)))
if (p == None) None
else
if (t == None) (p,"Main") else (p,t)
}
).filter(_ != None)
Consider this very simple snippet:
for (pTuple <- phoneVector) {
println(pTuple.getClass.getName)
println(pTuple)
//val pKey = pTuple._1.replaceAll("[^\\d]","")
associate() // stub prints "associate"
}
When I run it, I see output like this:
scala.Tuple2
((609) 954-3815,Mobile)
associate
When I uncomment the line with replaceAll(), compile fails:
....scala:57: value _1 is not a member of Product with Serializable
[error] val pKey = pTuple._1.replaceAll("[^\\d]","")
[error] ^
Why does it not recognize pTuple as a Tuple2 and treat it only as Product
OK, this compiles and produces the desired result. But it's too verbose. Can someone please demonstrate a more concise solution for dealing with this typesafe stuff?
for (pTuple <- phoneVector) {
println(pTuple.getClass.getName)
println(pTuple)
val pPhone = pTuple match {
case t:Tuple2[_,_] => t._1
case _ => None
}
val pKey = pPhone match {
case s:String => s.replaceAll("[^\\d]","")
case _ => None
}
println(pKey)
associate()
}
You can do:
for (pTuple <- phoneVector) {
val pPhone = pTuple match {
case (key, value) => key
case _ => None
}
val pKey = pPhone match {
case s:String => s.replaceAll("[^\\d]","")
case _ => None
}
println(pKey)
associate()
}
Or simply phoneVector.map(_._1.replaceAll("[^\\d]",""))
By changing the construction of phoneVector, as wrick's question implied, I've been able to eliminate the match/case stuff because Tuple is assured. Not thrilled by it, but Change is Hard, and Scala seems cool.
Now, it's still possible to slip a None value into either of the Tuple values. My match/case does not check for that, and I suspect that could lead to a runtime error in the replaceAll call. How is that allowed?
def killNS (s:Option[_]) = {
(s match {
case _:Some[_] => s.get
case _ => None
}) match {
case None => None
case "" => None
case s => s
}
}
val phoneVector = (
for (i <- 1 until 20) yield {
val p = killNS(r.get("Phone %d - Value" format(i)))
val t = killNS(r.get("Phone %d - Type" format(i)))
if (t == None) (p,"Main") else (p,t)
}
).filter(_._1 != None)
println(phoneVector)
println(name)
println
// Create the Neo4j nodes:
for (pTuple <- phoneVector) {
val pPhone = pTuple._1 match { case p:String => p }
val pType = pTuple._2
val pKey = pPhone.replaceAll(",.*","").replaceAll("[^\\d]","")
associate(Map("target"->Map("label"->"Phone","key"->pKey,
"dial"->pPhone),
"relation"->Map("label"->"IS_AT","key"->pType),
"source"->Map("label"->"Person","name"->name)
)
)
}
}

Slick: Option column filtering

I want to do something like this (this is a made-up example to simplify my actual problem):
def findByGender(isMale: Option[Boolean]) = {
People.filter(row => row.name.isNotNull && isMale match {
case Some(true) => row.wife.isNotNull // find rows where wife column is not null
case Some(false) => row.wife.isNull // find rows where wife column is null
case None => true // select everything
})
}
This does not compile because of the last "true". Any better way to do this?
You have to make it a Column[Boolean]:
def findByGender(isMale: Option[Boolean]) = {
People.filter(row => row.name.isNotNull && isMale match {
case Some(true) => row.wife.isNotNull // find rows where wife column is not null
case Some(false) => row.wife.isNull // find rows where wife column is null
case None => LiteralColumn(true) // select everything
})
}
If you're using Slick 3.3.x you can use the following solution:
def findByGender(isMale: Option[Boolean]) =
People
.filter(_.name.isDefined)
.filterIf(isMale == Some(true))(_.wife.isDefined)
.filterIf(isMale == Some(false))(_.wife.isEmpty)
or
def findByGender(isMale: Option[Boolean]) =
People
.filter(_.name.isDefined)
.filterOpt(isMale) {
case (row, true) => row.wife.isDefined
case (row, false) => row.wife.isEmpty
}
There are 3 cases:
isMale is defined and equal Some(true)
Result SQL: select * from people when (name is not null) and (wife is not null);
isMale is defined and equal Some(false):
Result SQL: select * from people when (name is not null) and (wife is null);
isMale is empty
Result SQL: select * from people when name is not null;

Slick left outer join fetching whole joined row as option

My join looks like this:
def byIdWithImage = for {
userId <- Parameters[Long]
(user, image) <- Users leftJoin RemoteImages on (_.imageId === _.id) if user.id === userId
} yield (user, image)
but slick fails at runtime when user.imageId is null
[SlickException: Read NULL value for column RemoteImage.url]
Changing the yield to
} yield (user, image.?)
gives me a compile time exception, it only works on individual columns
could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[image.type]
Would there be a different way to accomplish what I'm trying to do here? (in a single query)
With the code below you can put it like: yield (user, image.maybe)
case class RemoteImage(id: Long, url: URL)
class RemoteImages extends Table[RemoteImage]("RemoteImage") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def url = column[URL]("url", O.NotNull)
def * = id.? ~ url <> (RemoteImage.apply _, RemoteImage.unapply _)
def maybe = id.? ~ url.? <> (applyMaybe,unapplyBlank)
val unapplyBlank = (c:Option[RemoteImage])=>None
val applyMaybe = (t: (Option[Long],Option[URL])) => t match {
case (Some(id),Some(url)) => Some(RemoteImage(Some(id),url))
case _ => None
}
}
Off the top of my head, I'd use a custom mapped projection. Something like this:
case class RemoteImage(id: Long, url: URL)
def byIdWithImage = for {
userId <- Parameters[Long]
(user, image) <- Users leftJoin RemoteImages on (_.imageId === _.id) if user.id === userId
} yield (user, maybeRemoteImage(image.id.? ~ image.url.?))
def maybeRemoteImage(p: Projection2[Option[Long], Option[URL]]) = p <> (
for { id <- _: Option[Long]; url <- _: Option[URL] } yield RemoteImage(id, url),
(_: Option[RemoteImage]) map (i => (Some(i id), Some(i url)))
)
Using scalaz (and its ApplicativeBuilder) should help reduce some of that boilerplate.
I integrated helpers for this in my play-slick example app, which allow you to just call image.?
See the .? calls, the definition of ? and the definition of mapOption.