【问题标题】:How to use phantom dsl's optional columns?如何使用 phantom dsl 的可选列?
【发布时间】:2016-11-27 19:09:59
【问题描述】:

这个问题与之前的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 对象并添加新值,然后更新记录。在更新方法中写很多条件也是不现实的。如果我有很多列并且每一列都可以单独更新,我将不得不编写一个巨大的可能值组合列表。以下方法可能有效:

    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
    ......

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

【问题讨论】:

    标签: scala cassandra phantom-dsl


    【解决方案1】:

    从 Phantom 1.28.5 开始,您可以使用新的 setIfDefined 运算符来实现您想要的,这将为您提供您想要的,Update 子句仅考虑已定义的选项。

    您的更新方法现在变为:

    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()
    }
    

    【讨论】:

      【解决方案2】:

      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。我不记得了,但我认为那里可能有一个函数可以在您尝试阅读之前检查该列是否已定义,这样您就不必为 flow control™ 使用异常。

      【讨论】:

      • 当您选择使用Optional 列时,会自动为您添加column.parse(row).toOption。像集合之类的东西也会自动默认为空而不需要Optional
      猜你喜欢
      • 2016-09-11
      • 1970-01-01
      • 2016-10-10
      • 1970-01-01
      • 2021-06-22
      • 1970-01-01
      • 2018-02-22
      • 2017-02-19
      • 2016-04-10
      相关资源
      最近更新 更多