Slick on标记布尔类型的布尔运算

时间:2017-04-09 12:36:32

标签: scala slick

我已将标记类型实现为here

使用它们,我会标记所有模型的属性,这些属性或多或少是通用的(基元,String等)。当我使用Slick将模型映射到数据库时,我通常将它们定义为:

val isDeleted = column[Boolean @@ CompanyDeleted]("deleted", O.Default(false.tag))

而类型映射器我定义为:

implicit def taggedBooleanColumnType[U]: BaseColumnType[Boolean @@ U] =
  MappedColumnType.base[Boolean @@ U, Boolean](_.untag, _.tag[U])

它允许我对模型进行所有CRUD操作。但是,当我尝试例如:

def fetchById(companyId: Long @@ CompanyId): SqlAction[Option[Company], NoStream, Read] =
companies.filter(c => c.companyId === companyId && !c.isDeleted).result.headOption

这会失败,因为Rep[Boolean @@ CompanyDeleted]是nethier Rep[Boolean]还是Rep[Option[Boolean]]。当我创建暗示:

implicit def taggedBooleanExtensionMethods[P1, U](c: slick.lifted.Rep[P1]): TaggedBooleanExtensionMethods[P1, U] =
  new TaggedBooleanExtensionMethods[P1, U](c)

implicit def taggedBooleanColumnCanBeQueryCondition[U]: CanBeQueryCondition[slick.lifted.Rep[Boolean @@ U]] =
  new CanBeQueryCondition[slick.lifted.Rep[Boolean @@ U]] {
    def apply(value: slick.lifted.Rep[Boolean @@ U]) = value
  }

class TaggedBooleanExtensionMethods[P1, U](val c: Rep[P1])
    extends AnyVal
    with ExtensionMethods[Boolean @@ U, P1] {
  protected[this] implicit def b1Type = implicitly[TypedType[Boolean @@ U]]

  import slick.lifted.FunctionSymbolExtensionMethods._

  def &&[P2, R](b: Rep[P2])(implicit om: o#arg[Boolean @@ U, P2]#to[Boolean @@ U, R]) =
    om.column(Library.And, n, b.toNode)
  def ||[P2, R](b: Rep[P2])(implicit om: o#arg[Boolean @@ U, P2]#to[Boolean @@ U, R]) =
    om.column(Library.Or, n, b.toNode)
  def unary_! = Library.Not.column[P1](n)
}

我可以!_unary运营商,但不能&&||(因为Rep的类型不匹配)。

我的问题是:我可以提供/修改implicits以便能够对这些列执行布尔运算吗?我对||特别感兴趣,因为&&可以使用链式filter来完成。

1 个答案:

答案 0 :(得分:1)

It is not the pretties solution, but it is the best I came up with:

implicit class TaggedBooleanAsFirstOperand[P1, U](val c: Rep[P1 @@ U]) {
  private val em = new BooleanColumnExtensionMethods[P1](c.untagM)
  type o = OptionMapperDSL.arg[Boolean, P1]

  def @&&[P2, R](b: Rep[P2])(implicit om: o#arg[Boolean, P2]#to[Boolean, R]): Rep[R] = em.&&[P2, R](b)
  def @||[P2, R](b: Rep[P2])(implicit om: o#arg[Boolean, P2]#to[Boolean, R]): Rep[R] = em.||[P2, R](b)
  def unary_! : Rep[P1] = em.unary_!
}

implicit class TaggedBooleanAsSecondOperand[P1](val c: Rep[P1]) {
  private val em = new BooleanColumnExtensionMethods[P1](c)
  type o = OptionMapperDSL.arg[Boolean, P1]

  def &&@[P2, U, R](b: Rep[P2 @@ U])(implicit om: o#arg[Boolean, P2]#to[Boolean, R]): Rep[R] = em.&&[P2, R](b.untagM)
  def ||@[P2, U, R](b: Rep[P2 @@ U])(implicit om: o#arg[Boolean, P2]#to[Boolean, R]): Rep[R] = em.||[P2, R](b.untagM)
}

implicit class TaggedBooleanAsBothOperands[P1, U](val c: Rep[P1 @@ U]) {
  private val em = new BooleanColumnExtensionMethods[P1](c.untagM)
  type o = OptionMapperDSL.arg[Boolean, P1]

  def @&&@[P2, V, R](b: Rep[P2 @@ V])(implicit om: o#arg[Boolean, P2]#to[Boolean, R]): Rep[R] = em.&&[P2, R](b.untagM)
  def @||@[P2, V, R](b: Rep[P2 @@ V])(implicit om: o#arg[Boolean, P2]#to[Boolean, R]): Rep[R] = em.||[P2, R](b.untagM)
}

implicit def taggedBooleanColumnCanBeQueryCondition[U]: CanBeQueryCondition[Rep[Boolean @@ U]] =
  new CanBeQueryCondition[Rep[Boolean @@ U]] {
    def apply(value: Rep[Boolean @@ U]) = value.untagM
  }

Basically, once a column with tagged value is used as a boolean expression, the result will be already untagged into something that Slick can work on. Due to (a wild guess) conflicting implicits I couldn't use name && and || for all boolean operations, so I added @ on the side, which has (still) tagged column.

E.g. for companies table defined as:

val companyId = column[Long @@ CompanyId]("companyid", O.AutoInc, O.PrimaryKey)
val isDeleted = column[Boolean @@ CompanyDeleted]("deleted", O.Default(false.tag))

I can:

val companyId = 1L.tag[CompanyId]
// fetch deleted companies
companies.filter(_.isDeleted).result
// fetch company by id if it is deleted
// c.isDeleted is tagged, so && it requires @ on right
companies.filter(c => c.companyId === companyId &&@ c.isDeleted).result.headOption
// fetch company by id if it is NOT deleted
// ! made c.isDeleted untagged so there is no need for additional @
companies.filter(c => c.companyId === companyId && !c.isDeleted).result.headOption

I would prefer to not have to add @, but at least I can do queries without putting c.columnName.untagM everywhere.