How do you run a patch/partial database UPDATE in Scala Slick?

时间:2015-06-15 14:39:07

标签: scala slick

We'd like to run a patch/partial UPDATE with Slick (3.0.0) so that we only modify some of the fields in a record. Exactly which fields will be updated exactly will only be known at runtime.

For example, for a REST PATCH request.

Currently we run a SELECT first to get the original record then run an UPDATE but it would be nicer to do this in a single SQL statement.

Something like this:

def patchPerson(name: Option[String], age: Option[Int]) = {
   people.filter(_.name === "M Odersky")
       .map(p => 
           (name, age) match {
              case (Some(_), Some(_)) => (p.name, p.age)
              case (Some(_), None)    => (p.name)
              case (None   , Some(_)) => (p.age)
           }
       )
       .update(
           (name, age) match {
              case (Some(_), Some(_)) => (name.get, age.get)
              case (Some(_), None)    => (name.get)
              case (None   , Some(_)) => (age.get)
           }
       )
}

(Please ignore the ugly code here)

The above does not compile with the following error message:

No matching Shape found. Slick does not know how to map the given types. Possible causes: T in Table[T] does not match your * projection. Or you use an unsupported type in a Query (e.g. scala List). Required level: slick.lifted.FlatShapeLevel Source type: Object Unpacked type: T Packed type: G

And:

not enough arguments for method map: (implicit shape: slick.lifted.Shape[_ <: slick.lifted.FlatShapeLevel, Object, T, G])slick.lifted.Query[G,T,Seq]. Unspecified value parameter shape.

I assume this is because Slick expects the tuple length and type to match the results for both the filter and update functions.

We've tried using the Slick heterogeneous list class but this also seems to expect the length and types to match.

Is there a way to write this in Slick so that we can update an arbitrary number of fields in a record with one database call?

3 个答案:

答案 0 :(得分:3)

为什么不在构造更新查询之前进行模式匹配?

def patchPerson(name: Option[String], age: Option[Int]) = {
   val query = people.filter(_.name === "M Odersky")
   (name, age) match {
     case (Some(name), Some(age)) =>
       query.map(p => (p.name, p.age)).update(name, age)
     case (Some(name), None) =>
       query.map(p => p.name).update(name)
     case (None, Some(age)) =>
       query.map(p => p.age).update(age)
   }
}

答案 1 :(得分:1)

我最好的猜测是运行plain SQL query

即使SQL查询有两部分,关系数据库管理系统(postgresql,mysql等)也可以调整查询。

我不确定在这种情况下Slick是否能够进行优化,但在某些情况下它本身也是optimizes the queries

典型更新:

def updateRecord(id: Long, field1: Int) = {
    db.withSession {
      self.filter(_.id === id).map(_.field1).update(field1)
    }
}

执行更新类型需要更多的逻辑。如果您只是在运行时知道要更改哪些字段,请不要认为可以简化。但您可以使用记录中字段的现有值作为后备来强制进行更新(可能会导致更多数据库更新)

def updateRecord(id: Long, field1: Option[Int], field2: Option[Int]) = {
    db.withSession {
        self.filter(_.id === id).map(_.field1, _.field2).update(field1.getOrElse(existingValue1), field2.getOrElse(existingValue2)) 
    }
}

答案 2 :(得分:0)

你已经有了@ pedrorijo91和@thirstycow写的答案,但我会试着解释为什么这不起作用。

我没有使用光滑3但我猜测它,因为map函数不会返回一致类型,因为它可以运行更新。作为一个思想实验,如果你在地图上切换你的电话,你认为这种类型是什么?

val partialQuery:??? = people.filter(_.name === "M Odersky")
       .map(p => 
           (name, age) match {
              case (Some(_), Some(_)) => (p.name, p.age)
              case (Some(_), None)    => (p.name)
              case (None   , Some(_)) => (p.age)
           }
       );

val fullQuery:??? = partialQuery.update {
       (name, age) match {
          case (Some(_), Some(_)) => (name.get, age.get)
          case (Some(_), None)    => (name.get)
          case (None   , Some(_)) => (age.get)
       }    
}

匹配器在编译时返回不同的“形状”,我​​猜测它将恢复为任何类型。