Slick 3.0.0 - 仅使用非空值

时间:2016-02-26 10:31:20

标签: scala functional-programming slick slick-3.0

有一个包含列的表

class Data(tag: Tag) extends Table[DataRow](tag, "data") {
  def id = column[Int]("id", O.PrimaryKey)
  def name = column[String]("name")
  def state = column[State]("state")
  def price = column[Int]("price")

  def * = (id.?, name, state, price) <> ((DataRow.apply _).tupled, DataRow.unapply)
}

我想编写一个选择单行的函数,并更新提供的值不为空的列。

def update(id: Int, name: Option[String], state: Option[State], price: Option[Int])

例如

update(1, None, None, Some(5))只更新数据行1的价格,保持名称和状态不变

update(1, Some("foo"), None, Some(6))会更新名称和价格,但保持其状态不变。

我想可以使用一些智能映射,但我很难表达它,不知道它是如何根据输入(它们的值被定义)吐出不同长度的元组,因为它们是或多或少&#34;无关&#34;类。

def update(id: Int, name: Option[String], state: Option[State], price: Option[Int]) = {
  table.fiter(_.id == id). ???? .update(name, state, price)
}

3 个答案:

答案 0 :(得分:3)

我用以下方式解决了它。

以下实现仅在它是Product对象时才有效。

执行update语句,但Option类型为None,对象类型为null。

SIGPIPE

实施例

package slick.extensions

import slick.ast._
import slick.dbio.{ Effect, NoStream }
import slick.driver.JdbcDriver
import slick.jdbc._
import slick.lifted._
import slick.relational.{ CompiledMapping, ProductResultConverter, ResultConverter, TypeMappingResultConverter }
import slick.util.{ ProductWrapper, SQLBuilder }

import scala.language.{ existentials, higherKinds, implicitConversions }

trait PatchActionExtensionMethodsSupport { driver: JdbcDriver =>

  trait PatchActionImplicits {
    implicit def queryPatchActionExtensionMethods[U <: Product, C[_]](
        q: Query[_, U, C]
    ): PatchActionExtensionMethodsImpl[U] =
      createPatchActionExtensionMethods(updateCompiler.run(q.toNode).tree, ())
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////// Patch Actions
  ///////////////////////////////////////////////////////////////////////////////////////////////

  type PatchActionExtensionMethods[T <: Product] = PatchActionExtensionMethodsImpl[T]

  def createPatchActionExtensionMethods[T <: Product](tree: Node, param: Any): PatchActionExtensionMethods[T] =
    new PatchActionExtensionMethodsImpl[T](tree, param)

  class PatchActionExtensionMethodsImpl[T <: Product](tree: Node, param: Any) {
    protected[this] val ResultSetMapping(_, CompiledStatement(_, sres: SQLBuilder.Result, _),
      CompiledMapping(_converter, _)) = tree
    protected[this] val converter = _converter.asInstanceOf[ResultConverter[JdbcResultConverterDomain, Product]]
    protected[this] val TypeMappingResultConverter(childConverter, toBase, toMapped) = converter
    protected[this] val ProductResultConverter(elementConverters @ _ *) =
      childConverter.asInstanceOf[ResultConverter[JdbcResultConverterDomain, Product]]
    private[this] val updateQuerySplitRegExp = """(.*)(?<=set )((?:(?= where)|.)+)(.*)?""".r
    private[this] val updateQuerySetterRegExp = """[^\s]+\s*=\s*\?""".r

    /** An Action that updates the data selected by this query. */
    def patch(value: T): DriverAction[Int, NoStream, Effect.Write] = {
      val (seq, converters) = value.productIterator.zipWithIndex.toIndexedSeq
        .zip(elementConverters)
        .filter {
          case ((Some(_), _), _) => true
          case ((None, _), _) => false
          case ((null, _), _) => false
          case ((_, _), _) => true
        }
        .unzip

      val (products, indexes) = seq.unzip

      val newConverters = converters.zipWithIndex
        .map(c => (c._1, c._2 + 1))
        .map {
          case (c: BaseResultConverter[_], idx) => new BaseResultConverter(c.ti, c.name, idx)
          case (c: OptionResultConverter[_], idx) => new OptionResultConverter(c.ti, idx)
          case (c: DefaultingResultConverter[_], idx) => new DefaultingResultConverter(c.ti, c.default, idx)
          case (c: IsDefinedResultConverter[_], idx) => new IsDefinedResultConverter(c.ti, idx)
        }

      val productResultConverter =
        ProductResultConverter(newConverters: _*).asInstanceOf[ResultConverter[JdbcResultConverterDomain, Any]]
      val newConverter = TypeMappingResultConverter(productResultConverter, (p: Product) => p, (a: Any) => toMapped(a))

      val newValue: Product = new ProductWrapper(products)
      val newSql = sres.sql match {
        case updateQuerySplitRegExp(prefix, setter, suffix) =>
          val buffer = StringBuilder.newBuilder
          buffer.append(prefix)
          buffer.append(
            updateQuerySetterRegExp
              .findAllIn(setter)
              .zipWithIndex
              .filter(s => indexes.contains(s._2))
              .map(_._1)
              .mkString(", ")
          )
          buffer.append(suffix)
          buffer.toString()
      }

      new SimpleJdbcDriverAction[Int]("patch", Vector(newSql)) {
        def run(ctx: Backend#Context, sql: Vector[String]): Int =
          ctx.session.withPreparedStatement(sql.head) { st =>
            st.clearParameters
            newConverter.set(newValue, st)
            sres.setter(st, newConverter.width + 1, param)
            st.executeUpdate
          }
      }
    }
  }
}

https://gist.github.com/bad79s/1edf9ea83ba08c46add03815059acfca

答案 1 :(得分:1)

正如我评论的那样,问题类似于现有问题,但您似乎没有任何额外要求。

最简单的方法就是SELECT + UPDATE。例如,您在DataRow类中添加了一个补丁函数,用于定义您希望如何更新模型

      def patch(name: Option[String], state: Option[State], price: Option[Int]): Data {
         this.copy(name = name.getOrElse(this.name), ...)
      }

然后在repo类中添加partialUpdate方法

class DataRepo {
  private val Datas = TableQuery[Data]
  val db = ???

  def partialUpdate(id: Int, name: Option[String], state: Option[State], price: Option[Int]): Future[Int] = {
    val query = Datas.filter(_.id === id)
    for {
      data <- db.run(query.result.head)
      result <- db.run(query.update(data.patch(name, state, price)))
    } yield result
  }

}

如您所见,此解决方案的主要问题是有2个SQL语句,SELECT和UPDATE。

其他解决方案是使用纯SQL(http://slick.typesafe.com/doc/3.0.0/sql.html),但这当然会带来其他问题。

答案 2 :(得分:1)

构建于JonasAnso's answer,将其转换为slick v3.0 +,并将其置于事务中:

  def partialUpdate(id: Int, name: Option[String], login: Option[String]): Future[Int] = {
    val selectQ = users.filter(_.id === id)

    val query = selectQ.result.head.flatMap { data =>
      selectQ.update(data.patch(name, login))
    }

    db.run(query)
  }