Slick 3.0.0 - 仅使用非空值更新行
Slick 3.0.0 - update row with only non-null values
table 列
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)
}
我想编写一个函数来 select 单行,并更新提供的值不为空的列。
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))
会更新名称和价格,但保持其状态不变。
我想可以使用一些智能映射,但我很难表达它,不确定它如何根据输入(是否定义了它们的值)吐出不同长度的元组,因为它们是或多或少 "unrelated" 类.
def update(id: Int, name: Option[String], state: Option[State], price: Option[Int]) = {
table.fiter(_.id == id). ???? .update(name, state, price)
}
正如我评论的那样,这个问题与现有问题类似,但您似乎没有任何额外要求。
最简单的方法就是 SELECT + 更新。例如,您在 DataRow class 中添加一个补丁函数,定义您希望如何更新模型
def patch(name: Option[String], state: Option[State], price: Option[Int]): Data {
this.copy(name = name.getOrElse(this.name), ...)
}
并且您在您的存储库中添加了一个 partialUpdate 方法 class
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) 但这当然会带来其他问题。
基于 ,将其转换为 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)
}
我是按照下面的方法解决的
下面的实现只有在它是 Product 对象时才有效。
执行除了Option类型为None和object类型为null的更新语句。
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
}
}
}
}
}
例子
// Model
case class User(
id: Option[Int] = None,
name: Option[String] = None,
username: Option[String] = None,
password: Option[String] = None
)
// Table
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def username = column[String]("username")
def password = column[String]("password")
override def * = (id.?, name.?, username.?, password.?) <>(User.tupled, User.unapply)
}
// TableQuery
object Users extends TableQuery(new Users(_))
// CustomDriver
trait CustomDriver extends PostgresDriver with PatchActionExtensionMethodsSupport {
override val api: API = new API {}
trait API extends super.API with PatchActionImplicits
}
// Insert
Users += User(Some(1), Some("Test"), Some("test"), Some("1234"))
// User patch
Users.filter(_.id === 1).patch(User(name = Some("Change Name"), username = Some("")))
https://gist.github.com/bad79s/1edf9ea83ba08c46add03815059acfca
table 列
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)
}
我想编写一个函数来 select 单行,并更新提供的值不为空的列。
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))
会更新名称和价格,但保持其状态不变。
我想可以使用一些智能映射,但我很难表达它,不确定它如何根据输入(是否定义了它们的值)吐出不同长度的元组,因为它们是或多或少 "unrelated" 类.
def update(id: Int, name: Option[String], state: Option[State], price: Option[Int]) = {
table.fiter(_.id == id). ???? .update(name, state, price)
}
正如我评论的那样,这个问题与现有问题类似,但您似乎没有任何额外要求。
最简单的方法就是 SELECT + 更新。例如,您在 DataRow class 中添加一个补丁函数,定义您希望如何更新模型
def patch(name: Option[String], state: Option[State], price: Option[Int]): Data {
this.copy(name = name.getOrElse(this.name), ...)
}
并且您在您的存储库中添加了一个 partialUpdate 方法 class
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) 但这当然会带来其他问题。
基于
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)
}
我是按照下面的方法解决的
下面的实现只有在它是 Product 对象时才有效。
执行除了Option类型为None和object类型为null的更新语句。
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
}
}
}
}
}
例子
// Model
case class User(
id: Option[Int] = None,
name: Option[String] = None,
username: Option[String] = None,
password: Option[String] = None
)
// Table
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def username = column[String]("username")
def password = column[String]("password")
override def * = (id.?, name.?, username.?, password.?) <>(User.tupled, User.unapply)
}
// TableQuery
object Users extends TableQuery(new Users(_))
// CustomDriver
trait CustomDriver extends PostgresDriver with PatchActionExtensionMethodsSupport {
override val api: API = new API {}
trait API extends super.API with PatchActionImplicits
}
// Insert
Users += User(Some(1), Some("Test"), Some("test"), Some("1234"))
// User patch
Users.filter(_.id === 1).patch(User(name = Some("Change Name"), username = Some("")))
https://gist.github.com/bad79s/1edf9ea83ba08c46add03815059acfca