我已将标记类型实现为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
来完成。
答案 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.