gpt4 book ai didi

scala - Slick:动态创建查询连接/分离

转载 作者:行者123 更新时间:2023-12-04 13:11:09 30 4
gpt4 key购买 nike

我正在尝试为 Slick 表创建一个类型安全的动态 DSL,但不确定如何实现这一点。

用户可以通过以 form/json 格式发送过滤器来将过滤器发布到服务器,我需要使用所有这些来构建一个 Slick 查询。

所以基本上这意味着将表示我的过滤器的 Scala 案例类转换为 Slick 查询。

似乎“谓词”可以有 3 种不同的形状。我见过特征CanBeQueryCondition .我可以折叠这些不同的可能形状吗?

我看过扩展方法 &&||并且知道这与此有关,但我只是不知道该怎么做。

基本上,我有一个谓词列表,它采用以下类型:

(PatientTable) => Column[Option[Boolean]]

或者
(PatientTable) => Column[Boolean]

对我来说的问题是,对于具有 CanBeQueryCondition 的所有 3 种不同类型,没有一个单一的父类(super class)型。 ,所以我真的不知道如何用 && 折叠谓词一旦添加到列表中,这些不同形状的谓词采用非常通用的类型 List[(PatientTable) => Column[_ >: Boolean with Option[Boolean]]] .

另外,我不确定什么可以被视为 Slick 中的谓词。一个可组合谓词似乎是 Column[Boolean] ,但实际上是 filter方法只接受 (PatientTable) => Column[Boolean] 类型的参数

最佳答案

我正在用我最终构建的东西来回答我自己的问题。

让我们定义一个简单的 case 类和行映射器

case class User(
id: String = java.util.UUID.randomUUID().toString,
companyScopeId: String,
firstName: Option[String] = None,
lastName: Option[String] = None
)


class UserTable(tag: Tag) extends Table[User](tag,"USER") {
override def id = column[String]("id", O.PrimaryKey)
def companyScopeId = column[String]("company_scope_id", O.NotNull)
def firstName = column[Option[String]]("first_name", O.Nullable)
def lastName = column[Option[String]]("last_name", O.Nullable)

def * = (id, companyScopeId, firstName, lastName) <>
(User.tupled,User.unapply)
}

Slick 中谓词的概念

我认为“谓词”的概念可以放在 TableQuery.filter 中。 .但这种类型相当复杂,因为它是一个接受 Table 的函数。并返回具有隐式 CanBeQueryCondition 的类型

不幸的是,我有 3 种不同类型的 CanBeQueryCondition并将它们放在一个列表中以折叠成单个谓词似乎并不容易(即 filter 很容易应用,但 &&|| 运算符很难应用(就我尝试过的而言)) .但幸运的是,我们似乎可以轻松转换 BooleanColunm[Boolean]Column[Option[Boolean]].?扩展方法。

所以让我们定义我们的谓词类型:
type TablePredicate[Item, T <: Table[Item]] = T => Column[Option[Boolean]]

折叠谓词列表(即使用连词/析取,即组合 AND 和 OR 子句)

现在我们只有一种类型,因此我们可以轻松地将谓词列表折叠成一个
  // A predicate that never filter the result
def matchAll[Item, T <: Table[Item]]: TablePredicate[Item,T] = { table: T => LiteralColumn(1) === LiteralColumn(1) }

// A predicate that always filter the result
def matchNone[Item, T <: Table[Item]]: TablePredicate[Item,T] = { table: T => LiteralColumn(1) =!= LiteralColumn(1) }

def conjunction[Item, T <: Table[Item]](predicates: TraversableOnce[TablePredicate[Item, T]]): TablePredicate[Item,T] = {
if ( predicates.isEmpty ) matchAll[Item,T]
else {
predicates.reduce { (predicate1, predicate2) => table: T =>
predicate1(table) && predicate2(table)
}
}
}

def disjunction[Item, T <: Table[Item]](predicates: TraversableOnce[TablePredicate[Item, T]]): TablePredicate[Item,T] = {
if ( predicates.isEmpty ) matchNone[Item,T]
else {
predicates.reduce { (predicate1, predicate2) => table: T =>
predicate1(table) || predicate2(table)
}
}
}

动态过滤案例类

从这些谓词原语中,我们可以开始基于案例类创建我们的动态、可组合和类型安全的查询 DSL。
case class UserFilters(
companyScopeIds: Option[Set[String]] = None,
firstNames: Option[Set[String]] = None,
lastNames: Option[Set[String]] = None
) {

type UserPredicate = TablePredicate[User,UserTable]


def withFirstNames(firstNames: Set[String]): UserFilters = this.copy(firstNames = Some(firstNames))
def withFirstNames(firstNames: String*): UserFilters = withFirstNames(firstNames.toSet)

def withLastNames(lastNames: Set[String]): UserFilters = this.copy(lastNames = Some(lastNames))
def withLastNames(lastNames: String*): UserFilters = withLastNames(lastNames.toSet)

def withCompanyScopeIds(companyScopeIds: Set[String]): UserFilters = this.copy(companyScopeIds = Some(companyScopeIds))
def withCompanyScopeIds(companyScopeIds: String*): UserFilters = withCompanyScopeIds(companyScopeIds.toSet)


private def filterByFirstNames(firstNames: Set[String]): UserPredicate = { table: UserTable => table.firstName inSet firstNames }
private def filterByLastNames(lastNames: Set[String]): UserPredicate = { table: UserTable => table.lastName inSet lastNames }
private def filterByCompanyScopeIds(companyScopeIds: Set[String]): UserPredicate = { table: UserTable => (table.companyScopeId.? inSet companyScopeIds) }


def predicate: UserPredicate = {
// Build the list of predicate options (because filters are actually optional)
val optionalPredicates: List[Option[UserPredicate]] = List(
firstNames.map(filterByFirstNames(_)),
lastNames.map(filterByLastNames(_)),
companyScopeIds.map(filterByCompanyScopeIds(_))
)
// Filter the list to remove None's
val predicates: List[UserPredicate] = optionalPredicates.flatten
// By default, create a conjunction (AND) of the predicates of the represented by this case class
conjunction[User,UserTable](predicates)
}

}

注意 .?的用法为 companyScopeId允许将非可选列适合我们的 Slick 谓词定义的字段

使用 DSL
val Users = TableQuery(new UserTable(_))

val filter1 = UserFilters().withLastNames("lorber","silhol").withFirstName("robert")
val filter2 = UserFilters().withFirstName("sebastien")

val filter = disjunction[User,UserTable](Set(filter1.predicate,filter2.predicate))

val users = Users.filter(filter.predicate).list

// results in
// ( last_name in ("lorber","silhol") AND first_name in ("robert") )
// OR
// ( first_name in ("sebastien") )

结论

这远非完美,但它是初稿,至少可以给你一些灵感:) 我希望 Slick 能够更容易地构建其他查询 DSL 中非常常见的东西(如 Hibernate/JPA Criteria API)

另见此 Gist最新的解决方案

关于scala - Slick:动态创建查询连接/分离,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28281232/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com