如何使用幻影dsl的可选列?

时间:2016-07-24 10:07:04

标签: scala cassandra phantom-dsl

此问题与之前的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
    ......

但我不确定这是一个好习惯,因为如果你想在每次更新时处理更新失败,那将是一场噩梦。我应该如何使用可选列来实现我的需求?

2 个答案:

答案 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之类的内容来实现它们。在最低级别,您可以使用matchif语句来构建查询...

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做同样的事情,这应该让你知道它有多容易。棘手的部分是处理modifyand,但我确定在源头挖掘它们在封面下进行相同的操作,差异只是美观。

使用中:

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。我无法记住,但我认为在尝试阅读之前可能会有一个功能来检查列是否已定义,因此您不必使用流控制的例外。