此问题与之前的question有关。但由于这个问题已经结束,我必须创建一个单独的问题。 用例是这样的:我有一个包含5列的表。创建记录时,只需要2列。稍后用户将向同一记录添加更多信息。例如,具有以下结构的用户表:id |电话|电子邮件|信用|水平。当用户注册时,我只需要他们的电子邮件地址。将使用id和电子邮件创建用户。以后,用户想要添加手机号码,积分,系统也会在此用户有足够的积分时更新等级。 我创建了一个
case class User(id:UUID, phone:Option[String], email:Option[String],
credit:Option[Double], level:Option[String]
我也有
sealed class Users extends CassandraTable[Users, User] {
object id extends UUIDColumn(this) with PartitionKey[UUID]
object phone extends OptionalStringColumn(this)
object email extends OptionalStringColumn(this)
object credit extends OptionalDoubleColumn(this)
object level extends OptionalStringColumn(this)
def fromRow(row: Row): User = {
User(id(row), phone(row), email(row), credit(row), level(row))
}
}
我使用可选列吗? 我应该如何处理用户更新一个或多个特定列的用例?我尝试了像这样的更新方法
def updateUser(u: User): Future[ResultSet] = {
update.where(_.id eqs u.id).modify(_.phone setTo u.phone)
.and(_.email setTo u.email)
.and(_.credit setTo u.credit)
.and(_.level setTo u.level)
.consistencyLevel_=(ConsistencyLevel.QUORUM)
.future()
}
此方法不能很好地工作,因为您必须从id读取表并创建具有现有列值的User对象并添加新值,然后更新记录。在update方法中编写很多条件也是不切实际的。如果我有很多列,并且每列都可以单独更新,我将不得不写一个可能的值组合的巨大列表。以下方法可能有效:
if(u.phone != None) update.where(_.id eqs u.id).modify(_.phone setTo u.phone).future
if(u.email != None) update.where(_.id eqs u.id).modify(_.email setTo u.email).future
if(u.credit != None) update.where(_.id eqs u.id).modify(_.credit setTo u.credit).future
......
但我不确定这是一个好习惯,因为如果你想在每次更新时处理更新失败,那将是一场噩梦。我应该如何使用可选列来实现我的需求?
答案 0 :(得分:2)
从Phantom 1.28.5开始,你想要的是使用新的setIfDefined
运算符,它可以为你提供你想要的东西,Update
子句只考虑定义的选项。< / p>
您的更新方法现在变为:
def updateUser(u: User): Future[ResultSet] = {
update.where(_.id eqs u.id).modify(_.phone setTo u.phone)
.and(_.email setIfDefined u.email)
.and(_.credit setIfDefined u.credit)
.and(_.level setIfDefined u.level)
.consistencyLevel_=(ConsistencyLevel.QUORUM)
.future()
}
答案 1 :(得分:1)
Phantom中的查询是类,直到您通过调用.future
或.one
之类的内容来实现它们。在最低级别,您可以使用match
或if
语句来构建查询...
def updateUser(u: User): Future[ResultSet] = {
val baseQuery = update.where(_.id eqs u.id).modify(_.phone setTo u.phone)
val query2 = u.email match {
case Some(email) => baseQuery.and(_.email setTo email)
case None => baseQuery // don't do anything if none!
}
// ...
query5.future()
}
那种丑陋乏味但确实有效。
当我的模型具有Either
类型的属性时,我遇到了类似的情况。我最后为PhantomDSL编写了一个小扩展,我应该将其正式化并提交回来,但它可以很好地清理上面的混乱。它适用于INSERT
,但如果你想为UPDATE
做同样的事情,这应该让你知道它有多容易。棘手的部分是处理modify
与and
,但我确定在源头挖掘它们在封面下进行相同的操作,差异只是美观。
使用中:
table.insert
.value(_.foo, "foo")
.valueOpt(_.bar, Some("bar"))
.valueIf(_.buz, x, x.nonEmpty)
.matchOn(someEither) {
case (Left(Person(name, age), insert) => insert
.value(_.personName, name)
.value(_.personAge, age)
case (Right(Company(name, employeeCount), insert) => insert
.value(_.companyName, name)
.value(_.companyAge, age)
}
.future()
来源:
class RichInsert[T <: CassandraTable[T, _], R, S <: ConsistencyBound, P <: HList](val insert: InsertQuery[T,R,S,P]) {
def value[RR](col: T => AbstractColumn[RR], value: RR): RichInsert[T,R,S,P] =
new RichInsert(insert.value(col, value))
def valueIf[RR](col: T => AbstractColumn[RR], value: RR, clause: => Boolean): RichInsert[T,R,S,P] = {
if (clause) RichInsert(insert.value(col, value))
else this
}
def valueOpt[RR](col: T => AbstractColumn[RR], value: Option[RR]): RichInsert[T,R,S,P] = {
value match {
case Some(rr) => RichInsert(insert.value(col, rr))
case None => this
}
}
def matchOn[A](value: A)(pf: PartialFunction[(A, RichInsert[T,R,S,P]), RichInsert[T,R,S,P]]) =
if (pf.isDefinedAt((value, this))) pf.apply((value, this))
else this
}
object RichInsert {
def apply[T <: CassandraTable[T, _], R, S <: ConsistencyBound, P <: HList](insert: InsertQuery[T,R,S,P]): RichInsert[T,R,S,P] =
new RichInsert(insert)
}
implicit def insertQueryToRichInsert[T <: CassandraTable[T, _], R, S <: ConsistencyBound, P <: HList](insert: InsertQuery[T,R,S,P]): RichInsert[T,R,S,P] =
RichInsert(insert)
编辑 -
我忘记提到的一件事是,当您回读数据而不是column(row)
做column.parse(row)
时。您将获得Try[A]
,然后您可以column.parse(row).toOption
。我无法记住,但我认为在尝试阅读之前可能会有一个功能来检查列是否已定义,因此您不必使用流控制的例外。